i did thing oh god large commit
This commit is contained in:
parent
ddfe92fee4
commit
530bbf0587
38 changed files with 701 additions and 279 deletions
|
@ -198,7 +198,7 @@ CREATE TABLE admin.media (
|
||||||
id INTEGER DEFAULT nextval('sys.media_id_seq'::regclass) NOT NULL,
|
id INTEGER DEFAULT nextval('sys.media_id_seq'::regclass) NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
content BYTEA NOT NULL,
|
content BYTEA NOT NULL,
|
||||||
type TEXT NOT NULL,
|
mime TEXT NOT NULL,
|
||||||
created TIMESTAMP WITH TIME ZONE DEFAULT clock_timestamp() NOT NULL,
|
created TIMESTAMP WITH TIME ZONE DEFAULT clock_timestamp() NOT NULL,
|
||||||
modified TIMESTAMP WITH TIME ZONE DEFAULT clock_timestamp() NOT NULL
|
modified TIMESTAMP WITH TIME ZONE DEFAULT clock_timestamp() NOT NULL
|
||||||
);
|
);
|
||||||
|
@ -224,9 +224,12 @@ CREATE TYPE admin.user_media_type AS ENUM (
|
||||||
|
|
||||||
CREATE TABLE admin.user_media (
|
CREATE TABLE admin.user_media (
|
||||||
id INTEGER DEFAULT nextval('sys.user_media_id_seq'::regclass) NOT NULL,
|
id INTEGER DEFAULT nextval('sys.user_media_id_seq'::regclass) NOT NULL,
|
||||||
media_id INTEGER NOT NULL,
|
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
type admin.user_media_type NOT NULL
|
content BYTEA NOT NULL,
|
||||||
|
mime TEXT NOT NULL,
|
||||||
|
type admin.user_media_type NOT NULL,
|
||||||
|
created TIMESTAMP WITH TIME ZONE DEFAULT clock_timestamp() NOT NULL,
|
||||||
|
modified TIMESTAMP WITH TIME ZONE DEFAULT clock_timestamp() NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
ALTER TABLE admin.user_media OWNER TO xssbook;
|
ALTER TABLE admin.user_media OWNER TO xssbook;
|
||||||
|
@ -234,9 +237,6 @@ ALTER TABLE admin.user_media OWNER TO xssbook;
|
||||||
ALTER TABLE ONLY admin.user_media
|
ALTER TABLE ONLY admin.user_media
|
||||||
ADD CONSTRAINT user_media_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT user_media_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
ALTER TABLE ONLY admin.user_media
|
|
||||||
ADD CONSTRAINT user_media_media_id_fkey FOREIGN KEY (media_id) REFERENCES admin.media (id) ON DELETE CASCADE;
|
|
||||||
|
|
||||||
ALTER TABLE ONLY admin.user_media
|
ALTER TABLE ONLY admin.user_media
|
||||||
ADD CONSTRAINT user_media_user_id_fkey FOREIGN KEY (user_id) REFERENCES admin.user (id) ON DELETE CASCADE;
|
ADD CONSTRAINT user_media_user_id_fkey FOREIGN KEY (user_id) REFERENCES admin.user (id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
CREATE FUNCTION _api.serve_media(
|
CREATE FUNCTION _api.serve_system_media(
|
||||||
_media_id INTEGER
|
_media_id INTEGER
|
||||||
)
|
)
|
||||||
RETURNS sys."*/*"
|
RETURNS sys."*/*"
|
||||||
|
@ -8,34 +8,30 @@ DECLARE
|
||||||
_headers TEXT;
|
_headers TEXT;
|
||||||
_data BYTEA;
|
_data BYTEA;
|
||||||
BEGIN
|
BEGIN
|
||||||
|
|
||||||
SELECT FORMAT(
|
SELECT FORMAT(
|
||||||
'[{"Content-Type": "%s"},'
|
'[{"Content-Type": "%s"},'
|
||||||
'{"Content-Disposition": "inline; filename=\"%s\""},'
|
'{"Content-Disposition": "inline; filename=\"%s\""},'
|
||||||
'{"Cache-Control": "max-age=259200"}]'
|
'{"Cache-Control": "max-age=259200"}]'
|
||||||
, m.type, m.name)
|
, m.mime, m.name)
|
||||||
FROM admin.media m
|
FROM admin.media m
|
||||||
WHERE m.id = _media_id INTO _headers;
|
WHERE m.id = _media_id
|
||||||
|
INTO _headers;
|
||||||
PERFORM SET_CONFIG('response.headers', _headers, true);
|
|
||||||
|
|
||||||
SELECT m.content
|
SELECT m.content
|
||||||
FROM admin.media m
|
FROM admin.media m
|
||||||
WHERE m.id = _media_id
|
WHERE m.id = _media_id
|
||||||
INTO _data;
|
INTO _data;
|
||||||
|
|
||||||
IF FOUND THEN
|
IF _data IS NOT NULL THEN
|
||||||
|
PERFORM SET_CONFIG('response.headers', _headers, true);
|
||||||
RETURN(_data);
|
RETURN(_data);
|
||||||
ELSE
|
ELSE
|
||||||
PERFORM _api.raise(
|
PERFORM _api.raise_not_found();
|
||||||
_msg => 'api_not_found',
|
|
||||||
_err => 404
|
|
||||||
);
|
|
||||||
END IF;
|
END IF;
|
||||||
END
|
END
|
||||||
$BODY$;
|
$BODY$;
|
||||||
|
|
||||||
GRANT EXECUTE ON FUNCTION _api.serve_media(INTEGER)
|
GRANT EXECUTE ON FUNCTION _api.serve_system_media(INTEGER)
|
||||||
TO rest_anon, rest_user;
|
TO rest_anon, rest_user;
|
||||||
GRANT SELECT ON TABLE admin.media
|
GRANT SELECT ON TABLE admin.media
|
||||||
TO rest_anon, rest_user;
|
TO rest_anon, rest_user;
|
37
src/db/rest/media/_api_serve_user_media.sql
Normal file
37
src/db/rest/media/_api_serve_user_media.sql
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
CREATE FUNCTION _api.serve_user_media(
|
||||||
|
_media_id INTEGER
|
||||||
|
)
|
||||||
|
RETURNS sys."*/*"
|
||||||
|
LANGUAGE plpgsql VOLATILE
|
||||||
|
AS $BODY$
|
||||||
|
DECLARE
|
||||||
|
_headers TEXT;
|
||||||
|
_data BYTEA;
|
||||||
|
BEGIN
|
||||||
|
SELECT FORMAT(
|
||||||
|
'[{"Content-Type": "%s"},'
|
||||||
|
'{"Content-Disposition": "inline"},'
|
||||||
|
'{"Cache-Control": "max-age=259200"}]'
|
||||||
|
, m.mime)
|
||||||
|
FROM admin.user_media m
|
||||||
|
WHERE m.id = _media_id
|
||||||
|
INTO _headers;
|
||||||
|
|
||||||
|
SELECT m.content
|
||||||
|
FROM admin.user_media m
|
||||||
|
WHERE m.id = _media_id
|
||||||
|
INTO _data;
|
||||||
|
|
||||||
|
IF _data IS NOT NULL THEN
|
||||||
|
PERFORM SET_CONFIG('response.headers', _headers, true);
|
||||||
|
RETURN(_data);
|
||||||
|
ELSE
|
||||||
|
PERFORM _api.raise_not_found();
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$BODY$;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION _api.serve_user_media(INTEGER)
|
||||||
|
TO rest_anon, rest_user;
|
||||||
|
GRANT SELECT ON TABLE admin.user_media
|
||||||
|
TO rest_anon, rest_user;
|
41
src/db/rest/media/_api_serve_user_or_default_media.sql
Normal file
41
src/db/rest/media/_api_serve_user_or_default_media.sql
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
CREATE FUNCTION _api.serve_user_or_default_media(
|
||||||
|
_user_id INTEGER,
|
||||||
|
_type admin.user_media_type,
|
||||||
|
_default TEXT
|
||||||
|
)
|
||||||
|
RETURNS sys."*/*"
|
||||||
|
LANGUAGE plpgsql VOLATILE
|
||||||
|
AS $BODY$
|
||||||
|
DECLARE
|
||||||
|
_media_id INTEGER;
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
SELECT id
|
||||||
|
FROM admin.user_media m
|
||||||
|
WHERE m.type = _type
|
||||||
|
AND m.user_id = _user_id
|
||||||
|
INTO _media_id;
|
||||||
|
|
||||||
|
IF FOUND THEN
|
||||||
|
RETURN _api.serve_user_media(_media_id);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT id
|
||||||
|
FROM admin.media m
|
||||||
|
WHERE m.name = _default
|
||||||
|
INTO _media_id;
|
||||||
|
|
||||||
|
IF FOUND THEN
|
||||||
|
RETURN _api.serve_system_media(_media_id);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
PERFORM _api_raise_not_found();
|
||||||
|
END
|
||||||
|
$BODY$;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION _api.serve_user_or_default_media(INTEGER, admin.user_media_type, TEXT)
|
||||||
|
TO rest_anon, rest_user;
|
||||||
|
GRANT SELECT ON TABLE admin.user_media
|
||||||
|
TO rest_anon, rest_user;
|
||||||
|
GRANT SELECT ON TABLE admin.media
|
||||||
|
TO rest_anon, rest_user;
|
|
@ -5,32 +5,16 @@ RETURNS sys."*/*"
|
||||||
LANGUAGE plpgsql VOLATILE
|
LANGUAGE plpgsql VOLATILE
|
||||||
AS $BODY$
|
AS $BODY$
|
||||||
DECLARE
|
DECLARE
|
||||||
_id INTEGER;
|
_default TEXT;
|
||||||
_mod INTEGER;
|
|
||||||
_name TEXT;
|
|
||||||
BEGIN
|
BEGIN
|
||||||
SELECT media_id INTO _id
|
_default := 'default_avatar_' || MOD(user_id, 25) || '.png';
|
||||||
FROM admin.user_media m
|
RETURN _api.serve_user_or_default_media(
|
||||||
WHERE m.user_id = profile_avatar.user_id
|
user_id,
|
||||||
AND type = 'avatar'::admin.user_media_type;
|
'avatar'::admin.user_media_type,
|
||||||
|
_default
|
||||||
-- get default if not exists
|
);
|
||||||
IF NOT FOUND THEN
|
|
||||||
_mod = MOD(user_id, 24);
|
|
||||||
_name = 'default_avatar_' || _mod || '.png';
|
|
||||||
|
|
||||||
SELECT id INTO _id
|
|
||||||
FROM admin.media
|
|
||||||
WHERE name = _name;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
RETURN _api.serve_media(_id);
|
|
||||||
END
|
END
|
||||||
$BODY$;
|
$BODY$;
|
||||||
|
|
||||||
GRANT EXECUTE ON FUNCTION api.profile_avatar(INTEGER)
|
GRANT EXECUTE ON FUNCTION api.profile_avatar(INTEGER)
|
||||||
TO rest_anon, rest_user;
|
TO rest_anon, rest_user;
|
||||||
GRANT SELECT ON TABLE admin.user_media
|
|
||||||
TO rest_anon, rest_user;
|
|
||||||
GRANT SELECT ON TABLE admin.media
|
|
||||||
TO rest_anon, rest_user;
|
|
||||||
|
|
|
@ -4,10 +4,21 @@ CREATE FUNCTION api.profile_banner(
|
||||||
RETURNS sys."*/*"
|
RETURNS sys."*/*"
|
||||||
LANGUAGE plpgsql VOLATILE
|
LANGUAGE plpgsql VOLATILE
|
||||||
AS $BODY$
|
AS $BODY$
|
||||||
|
DECLARE
|
||||||
|
_default TEXT;
|
||||||
BEGIN
|
BEGIN
|
||||||
PERFORM _api.raise_deny();
|
_default := 'default_banner_' || MOD(user_id, 25) || '.png';
|
||||||
|
RETURN _api.serve_user_or_default_media(
|
||||||
|
user_id,
|
||||||
|
'banner'::admin.user_media_type,
|
||||||
|
_default
|
||||||
|
);
|
||||||
END
|
END
|
||||||
$BODY$;
|
$BODY$;
|
||||||
|
|
||||||
GRANT EXECUTE ON FUNCTION api.profile_banner(INTEGER)
|
GRANT EXECUTE ON FUNCTION api.profile_banner(INTEGER)
|
||||||
TO rest_anon, rest_user;
|
TO rest_anon, rest_user;
|
||||||
|
GRANT SELECT ON TABLE admin.user_media
|
||||||
|
TO rest_anon, rest_user;
|
||||||
|
GRANT SELECT ON TABLE admin.media
|
||||||
|
TO rest_anon, rest_user;
|
||||||
|
|
|
@ -6,7 +6,9 @@ CREATE VIEW api.post AS
|
||||||
p.created,
|
p.created,
|
||||||
p.modified,
|
p.modified,
|
||||||
COALESCE(c.cc, 0)
|
COALESCE(c.cc, 0)
|
||||||
AS comment_count
|
AS comment_count,
|
||||||
|
COALESCE(l.lc, 0)
|
||||||
|
AS like_count
|
||||||
FROM
|
FROM
|
||||||
admin.post p
|
admin.post p
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
|
@ -20,6 +22,17 @@ CREATE VIEW api.post AS
|
||||||
) c
|
) c
|
||||||
ON
|
ON
|
||||||
p.id = c.post_id
|
p.id = c.post_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
COUNT(l.id) as lc,
|
||||||
|
l.post_id
|
||||||
|
FROM
|
||||||
|
admin.like l
|
||||||
|
GROUP BY
|
||||||
|
l.post_id
|
||||||
|
) l
|
||||||
|
ON
|
||||||
|
p.id = l.post_id
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
admin.user u
|
admin.user u
|
||||||
ON
|
ON
|
||||||
|
|
|
@ -15,7 +15,6 @@ GRANT USAGE ON SCHEMA _api TO rest_anon, rest_user;
|
||||||
|
|
||||||
-- util
|
-- util
|
||||||
\i /db/rest/util/_api_trim.sql;
|
\i /db/rest/util/_api_trim.sql;
|
||||||
\i /db/rest/util/_api_serve_media.sql;
|
|
||||||
\i /db/rest/util/_api_raise.sql;
|
\i /db/rest/util/_api_raise.sql;
|
||||||
\i /db/rest/util/_api_raise_null.sql;
|
\i /db/rest/util/_api_raise_null.sql;
|
||||||
\i /db/rest/util/_api_raise_unique.sql;
|
\i /db/rest/util/_api_raise_unique.sql;
|
||||||
|
@ -47,6 +46,9 @@ GRANT USAGE ON SCHEMA _api TO rest_anon, rest_user;
|
||||||
\i /db/rest/like/api_like_delete.sql;
|
\i /db/rest/like/api_like_delete.sql;
|
||||||
|
|
||||||
-- media
|
-- media
|
||||||
|
\i /db/rest/media/_api_serve_user_media.sql;
|
||||||
|
\i /db/rest/media/_api_serve_system_media.sql;
|
||||||
|
\i /db/rest/media/_api_serve_user_or_default_media.sql;
|
||||||
\i /db/rest/media/api_profile_avatar.sql;
|
\i /db/rest/media/api_profile_avatar.sql;
|
||||||
\i /db/rest/media/api_profile_banner.sql;
|
\i /db/rest/media/api_profile_banner.sql;
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,74 @@ CREATE VIEW api.user AS
|
||||||
u.profile_bio,
|
u.profile_bio,
|
||||||
u.created,
|
u.created,
|
||||||
u.modified,
|
u.modified,
|
||||||
u.seen
|
u.seen,
|
||||||
|
COALESCE(f.fc, 0)
|
||||||
|
AS follower_count,
|
||||||
|
COALESCE(fl.fc, 0)
|
||||||
|
AS followed_count,
|
||||||
|
COALESCE(c.cc, 0)
|
||||||
|
AS comment_count,
|
||||||
|
COALESCE(p.pc, 0)
|
||||||
|
AS post_count,
|
||||||
|
COALESCE(l.lc, 0)
|
||||||
|
AS like_count
|
||||||
FROM
|
FROM
|
||||||
admin.user u
|
admin.user u
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
COUNT(f.id) as fc,
|
||||||
|
f.followee_id
|
||||||
|
FROM
|
||||||
|
admin.follow f
|
||||||
|
GROUP BY
|
||||||
|
f.followee_id
|
||||||
|
) f
|
||||||
|
ON
|
||||||
|
u.id = f.followee_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
COUNT(fl.id) as fc,
|
||||||
|
fl.follower_id
|
||||||
|
FROM
|
||||||
|
admin.follow fl
|
||||||
|
GROUP BY
|
||||||
|
fl.follower_id
|
||||||
|
) fl
|
||||||
|
ON
|
||||||
|
u.id = fl.follower_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
COUNT(c.id) as cc,
|
||||||
|
c.user_id
|
||||||
|
FROM
|
||||||
|
admin.comment c
|
||||||
|
GROUP BY
|
||||||
|
c.user_id
|
||||||
|
) c
|
||||||
|
ON
|
||||||
|
u.id = c.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
COUNT(p.id) as pc,
|
||||||
|
p.user_id
|
||||||
|
FROM
|
||||||
|
admin.post p
|
||||||
|
GROUP BY
|
||||||
|
p.user_id
|
||||||
|
) p
|
||||||
|
ON
|
||||||
|
u.id = p.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
COUNT(l.id) as lc,
|
||||||
|
l.user_id
|
||||||
|
FROM
|
||||||
|
admin.like l
|
||||||
|
GROUP BY
|
||||||
|
l.user_id
|
||||||
|
) l
|
||||||
|
ON
|
||||||
|
u.id = l.user_id
|
||||||
WHERE
|
WHERE
|
||||||
u.deleted <> TRUE;
|
u.deleted <> TRUE;
|
||||||
|
|
||||||
|
|
16
src/db/rest/util/_api_raise_not_found.sql
Normal file
16
src/db/rest/util/_api_raise_not_found.sql
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
CREATE FUNCTION _api.raise_not_found()
|
||||||
|
RETURNS BOOLEAN
|
||||||
|
LANGUAGE plpgsql VOLATILE
|
||||||
|
AS $BODY$
|
||||||
|
BEGIN
|
||||||
|
PERFORM _api.raise(
|
||||||
|
_msg => 'api_not_found',
|
||||||
|
_err => 404
|
||||||
|
);
|
||||||
|
|
||||||
|
RETURN TRUE;
|
||||||
|
END
|
||||||
|
$BODY$;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION _api.raise_not_found()
|
||||||
|
TO rest_anon, rest_user;
|
|
@ -170,6 +170,21 @@ a, button {
|
||||||
color: var(--blue-alt);
|
color: var(--blue-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-border::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
bottom: -1px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--blue-alt);
|
||||||
|
}
|
||||||
|
|
||||||
input.btn:focus {
|
input.btn:focus {
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -205,6 +220,7 @@ input.btn:focus {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
|
@ -224,7 +240,7 @@ input.btn:focus {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 2;
|
z-index: 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
@media (min-width: 800px) {
|
||||||
|
@ -274,6 +290,11 @@ input.btn:focus {
|
||||||
.nav-center .btn.active {
|
.nav-center .btn.active {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav .btn-border::before {
|
||||||
|
background: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-right .image-loading {
|
.nav-right .image-loading {
|
||||||
|
@ -305,6 +326,10 @@ input.btn:focus {
|
||||||
animation: shimmer 1s linear infinite;
|
animation: shimmer 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-loaded {
|
||||||
|
background-color: var(--base);
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: var(--surface0);
|
background-color: var(--surface0);
|
||||||
border-radius: .5rem;
|
border-radius: .5rem;
|
||||||
|
@ -393,6 +418,14 @@ input.btn:focus {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
animation: fadeIn .1s, slideInModal .1s linear;
|
animation: fadeIn .1s, slideInModal .1s linear;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
.modal {
|
||||||
|
min-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideInModal {
|
@keyframes slideInModal {
|
||||||
|
|
|
@ -2,11 +2,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
padding: 1rem;
|
||||||
|
padding-bottom: 0;
|
||||||
.card {
|
|
||||||
width: 40rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-post-modal textarea {
|
.new-post-modal textarea {
|
||||||
|
|
|
@ -11,42 +11,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#people-container {
|
#people-container {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr) );
|
||||||
|
grid-auto-rows: max-content;
|
||||||
|
grid-gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile {
|
||||||
|
width: 16rem;
|
||||||
|
text-decoration: none;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
padding: 1rem 2rem;
|
|
||||||
padding-bottom: 0;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
max-width: 90rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile {
|
|
||||||
width: 25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(max-width: 1400px) {
|
|
||||||
#people-container {
|
|
||||||
max-width: 70rem;
|
|
||||||
}
|
|
||||||
.profile {
|
|
||||||
width: 25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(max-width: 1000px) {
|
|
||||||
#people-container {
|
|
||||||
max-width: 50rem;
|
|
||||||
}
|
|
||||||
.profile {
|
|
||||||
width: 20rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile {
|
|
||||||
margin: 1rem;
|
|
||||||
text-decoration: none;
|
|
||||||
height: fit-content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile:hover {
|
.profile:hover {
|
||||||
|
@ -54,16 +32,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile strong {
|
.profile strong {
|
||||||
font-size: 2rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .pfp, .profile .pfp img {
|
.profile .pfp, .profile .pfp img {
|
||||||
padding: none;
|
padding: none;
|
||||||
margin: none;
|
margin: none;
|
||||||
height: 6rem;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
border-radius: .3rem;
|
border-radius: .3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile .pfp {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
td:nth-child(1) {
|
td:nth-child(1) {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--subtext);
|
color: var(--subtext);
|
||||||
|
|
|
@ -8,6 +8,22 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post, #new-post {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
width: 40rem;
|
||||||
|
}
|
||||||
|
|
||||||
.post {
|
.post {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media(max-width: 40rem) {
|
||||||
|
.post, #new-post {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.post .likes {
|
||||||
|
display: block;
|
||||||
|
padding-top: .25rem;
|
||||||
|
}
|
||||||
|
|
|
@ -4,17 +4,34 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#profile-header {
|
#profile-header-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
background-color: var(--surface0);
|
background-color: var(--surface0);
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid var(--surface1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#profile-header {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 80rem;
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#profile-header .banner {
|
#profile-header .banner {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 20rem;
|
min-height: 30rem;
|
||||||
aspect-ratio: 5;
|
aspect-ratio: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#profile-header .banner img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
#profile-header .info .pfp-wrapper .pfp,
|
#profile-header .info .pfp-wrapper .pfp,
|
||||||
#profile-header .info .pfp-wrapper .pfp img {
|
#profile-header .info .pfp-wrapper .pfp img {
|
||||||
height: 12.5rem;
|
height: 12.5rem;
|
||||||
|
@ -32,7 +49,7 @@
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -2.5rem;
|
top: -2.5rem;
|
||||||
left: 2rem;
|
left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#profile-header .info .content {
|
#profile-header .info .content {
|
||||||
|
@ -65,13 +82,27 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tab-posts,
|
.tab {
|
||||||
#post-container {
|
max-width: 80rem;
|
||||||
width: 40rem;
|
width: 100%;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#post-container {
|
||||||
|
max-width: 40rem;
|
||||||
|
width: 100%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-bottom: -1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#post-container .post {
|
#post-container .post {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td:nth-child(1) {
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@ var $$ = (selector) => {
|
||||||
'click',
|
'click',
|
||||||
'submit',
|
'submit',
|
||||||
'each',
|
'each',
|
||||||
'error'
|
'error',
|
||||||
|
'one'
|
||||||
];
|
];
|
||||||
|
|
||||||
let vtable = {};
|
let vtable = {};
|
||||||
|
@ -46,9 +47,9 @@ var $$ = (selector) => {
|
||||||
let config = { childList: true, subtree: true };
|
let config = { childList: true, subtree: true };
|
||||||
let MutationObserver = window.MutationObserver;
|
let MutationObserver = window.MutationObserver;
|
||||||
let observer = new MutationObserver(onMutate);
|
let observer = new MutationObserver(onMutate);
|
||||||
|
|
||||||
observer.observe(document.body, config);
|
observer.observe(document.body, config);
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,3 +122,14 @@ $.ajaxSetup({
|
||||||
})(),
|
})(),
|
||||||
error: errorToastAjax
|
error: errorToastAjax
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var onImgLoad = function(me) {
|
||||||
|
me.parentElement.classList.remove('image-loading');
|
||||||
|
me.parentElement.classList.add('image-loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
var onImgError = function(me) {
|
||||||
|
me.parentElement.classList.remove('image-loading');
|
||||||
|
me.parentElement.classList.add('image-loaded');
|
||||||
|
me.remove();
|
||||||
|
}
|
||||||
|
|
|
@ -45,8 +45,15 @@ $$('#action-load-posts').on('click', function() {
|
||||||
let pageSize = Number(me.attr('pageSize'));
|
let pageSize = Number(me.attr('pageSize'));
|
||||||
let postCount = Number(me.attr('postCount'));
|
let postCount = Number(me.attr('postCount'));
|
||||||
let postMax = Number(me.attr('postMax'));
|
let postMax = Number(me.attr('postMax'));
|
||||||
|
let filterUid = me.attr('userId');
|
||||||
|
|
||||||
let url = '/_util/post/posts?page=' + page + '&max=' + postMax;
|
let url = '/_util/post/posts?page=' + page + '&max=' + postMax;
|
||||||
|
|
||||||
|
if (!isNaN(filterUid)) {
|
||||||
|
console.log(filterUid);
|
||||||
|
url += '&user_id=' + filterUid;
|
||||||
|
}
|
||||||
|
|
||||||
$.get(url, function (data) {
|
$.get(url, function (data) {
|
||||||
if (data === '') {
|
if (data === '') {
|
||||||
me.remove();
|
me.remove();
|
||||||
|
@ -101,13 +108,27 @@ $$('.action-like').on('click', function() {
|
||||||
let like_id = me.attr('likeId');
|
let like_id = me.attr('likeId');
|
||||||
let post_id = me.attr('postId');
|
let post_id = me.attr('postId');
|
||||||
|
|
||||||
|
const updateLiked = (liked) => {
|
||||||
|
let post = me.closest('.post');
|
||||||
|
let likes = post.find('.likes');
|
||||||
|
let count = likes.find('.count');
|
||||||
|
|
||||||
|
let c = Number(count[0].textContent);
|
||||||
|
c += liked ? 1 : -1;
|
||||||
|
count[0].textContent = c;
|
||||||
|
}
|
||||||
|
|
||||||
const onPatch = () => {
|
const onPatch = () => {
|
||||||
|
let liked = me.hasClass('btn-blue');
|
||||||
me.toggleClass('btn-blue');
|
me.toggleClass('btn-blue');
|
||||||
|
updateLiked(!liked);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPost = (data) => {
|
const onPost = (data) => {
|
||||||
|
let liked = me.hasClass('btn-blue');
|
||||||
me.attr('likeId', data[0].id + '');
|
me.attr('likeId', data[0].id + '');
|
||||||
me.toggleClass('btn-blue');
|
me.toggleClass('btn-blue');
|
||||||
|
updateLiked(!liked);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (like_id) {
|
if (like_id) {
|
||||||
|
|
|
@ -79,9 +79,9 @@ class Post_controller extends Controller {
|
||||||
->where('p.id')->le($max);
|
->where('p.id')->le($max);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($uid) {
|
if ($filter_uid) {
|
||||||
$query = $query
|
$query = $query
|
||||||
->where('p.user_id')->eq($uid);
|
->where('p.user_id')->eq($filter_uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
$posts = $query
|
$posts = $query
|
||||||
|
@ -106,9 +106,9 @@ class Post_controller extends Controller {
|
||||||
->select('COUNT(p.id) as pc')
|
->select('COUNT(p.id) as pc')
|
||||||
->from('api.post p');
|
->from('api.post p');
|
||||||
|
|
||||||
if ($uid) {
|
if ($filter_uid) {
|
||||||
$query = $query
|
$query = $query
|
||||||
->where('p.user_id')->eq($uid);
|
->where('p.user_id')->eq($filter_uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
$pc = $query
|
$pc = $query
|
||||||
|
|
|
@ -17,10 +17,17 @@ class People_controller extends Controller {
|
||||||
parent::index();
|
parent::index();
|
||||||
$data = $this->people_model->get_data();
|
$data = $this->people_model->get_data();
|
||||||
$this->view('header', $data);
|
$this->view('header', $data);
|
||||||
|
$this->view('apps/people/header', $data);
|
||||||
$this->view('apps/people/main', $data);
|
$this->view('apps/people/main', $data);
|
||||||
|
$this->view('apps/people/footer', $data);
|
||||||
$this->view('footer', $data);
|
$this->view('footer', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function content(): void {
|
||||||
|
$data = $this->people_model->get_data();
|
||||||
|
$this->view('apps/people/main', $data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string,mixed>
|
* @return array<string,mixed>
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -7,12 +7,16 @@ class Profile_controller extends Controller {
|
||||||
// the format model
|
// the format model
|
||||||
protected $format_model;
|
protected $format_model;
|
||||||
|
|
||||||
// the post model
|
// the post controller
|
||||||
protected $post_controller;
|
protected $post_controller;
|
||||||
|
|
||||||
|
// the people controller
|
||||||
|
protected $people_controller;
|
||||||
|
|
||||||
function __construct($load) {
|
function __construct($load) {
|
||||||
parent::__construct($load);
|
parent::__construct($load);
|
||||||
$this->profile_model = $this->load->model('apps/profile');
|
$this->profile_model = $this->load->model('apps/profile');
|
||||||
|
$this->people_controller = $this->load->controller('apps/people');
|
||||||
$this->format_model = $this->load->model('format');
|
$this->format_model = $this->load->model('format');
|
||||||
$this->post_controller = $this->load->controller('_util/post');
|
$this->post_controller = $this->load->controller('_util/post');
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,40 +7,32 @@ class People_model extends Model {
|
||||||
parent::__construct($load);
|
parent::__construct($load);
|
||||||
$this->request_model = $this->load->model('request');
|
$this->request_model = $this->load->model('request');
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
private function get_filted_query($select) {
|
* @param mixed $select
|
||||||
$filter_username = $this->request_model->get_str('filter_username', FALSE);
|
*/
|
||||||
$filter_fisrt_name = $this->request_model->get_str('filter_first_name', FALSE);
|
private function get_filted_query($select): DatabaseQuery {
|
||||||
$filter_last_name = $this->request_model->get_str('filter_last_name', FALSE);
|
$filter_type = $this->request_model->get_str('filter', FALSE);
|
||||||
$filter_email = $this->request_model->get_str('filter_email', FALSE);
|
$filter_uid = $this->request_model->get_int('uid', FALSE);
|
||||||
$max = $this->request_model->get_int('max', FALSE);
|
$max = $this->request_model->get_int('max', FALSE);
|
||||||
|
|
||||||
$query = $this->db
|
$query = $this->db
|
||||||
->select($select)
|
->select($select)
|
||||||
->from('api.user u');
|
->from('api.user u');
|
||||||
|
|
||||||
if ($filter_username) {
|
if ($filter_type && $filter_uid) {
|
||||||
|
switch ($filter_type) {
|
||||||
|
case 'follower': {
|
||||||
$query = $query
|
$query = $query
|
||||||
->where('u.username')
|
->join('admin.follow f', 'f.follower_id = u.id AND f.followee_id', 'INNER')
|
||||||
->like('%' . $filter_username . '%');
|
->eq($filter_uid);
|
||||||
}
|
} break;
|
||||||
|
|
||||||
if ($filter_fisrt_name) {
|
case 'followee': {
|
||||||
$query = $query
|
$query = $query
|
||||||
->where('u.first_name')
|
->join('admin.follow f', 'f.followee_id = u.id AND f.follower_id', 'INNER')
|
||||||
->like('%'. $filter_fisrt_name . '%');
|
->eq($filter_uid);
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($filter_last_name) {
|
|
||||||
$query = $query
|
|
||||||
->where('u.last_name')
|
|
||||||
->like('%' . $filter_last_name . '%');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($filter_email) {
|
|
||||||
$query = $query
|
|
||||||
->where('u.email')
|
|
||||||
->like('%' . $filter_email . '%');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($max) {
|
if ($max) {
|
||||||
|
@ -52,12 +44,15 @@ class People_model extends Model {
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string,mixed>
|
||||||
|
*/
|
||||||
public function get_users(): array {
|
public function get_users(): array {
|
||||||
$page = $this->request_model->get_int('page', 0);
|
$page = $this->request_model->get_int('page', 0);
|
||||||
$page_size = 24;
|
$page_size = 24;
|
||||||
$offset = $page_size * $page;
|
$offset = $page_size * $page;
|
||||||
|
|
||||||
$users = $this->get_filted_query('*')
|
$users = $this->get_filted_query('u.*')
|
||||||
->order_by('u.id', 'DESC')
|
->order_by('u.id', 'DESC')
|
||||||
->offset($offset)
|
->offset($offset)
|
||||||
->limit($page_size)
|
->limit($page_size)
|
||||||
|
@ -72,11 +67,16 @@ class People_model extends Model {
|
||||||
$max = max($max, $user['id']);
|
$max = max($max, $user['id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$filter_type = $this->request_model->get_str('filter', FALSE);
|
||||||
|
$filter_uid = $this->request_model->get_int('uid', FALSE);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'users' => $users,
|
'users' => $users,
|
||||||
'count' => $count,
|
'count' => $count,
|
||||||
'page_size' => $page_size,
|
'page_size' => $page_size,
|
||||||
'max_id' => $max
|
'max_id' => $max,
|
||||||
|
'filter_type' => $filter_type || '',
|
||||||
|
'filter_uid' => $filter_uid || ''
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<?php if ($self): ?>
|
<?php if ($self): ?>
|
||||||
<div id="new-post" class="card">
|
<div id="new-post" class="card">
|
||||||
<div class="row grow">
|
<div class="row grow">
|
||||||
<?php $this->view('template/pfp', array('user' => $self))?>
|
<?=pfp($self)?>
|
||||||
<a
|
<a
|
||||||
id="action-new-post"
|
id="action-new-post"
|
||||||
class="btn btn-alt btn-wide ml"
|
class="btn btn-alt btn-wide ml"
|
||||||
|
|
|
@ -4,32 +4,12 @@
|
||||||
class="card profile"
|
class="card profile"
|
||||||
href="/profile?id=<?=$user['id']?>"
|
href="/profile?id=<?=$user['id']?>"
|
||||||
>
|
>
|
||||||
<div class="row">
|
<div class="col">
|
||||||
<?php $this->view('template/pfp', array('user' => $user, 'link' => FALSE)); ?>
|
<?=pfp($user, FALSE)?>
|
||||||
<div class="col ml">
|
<div class="col ml">
|
||||||
<strong class=""><?=$this->format_model->name($user)?></strong>
|
<strong class=""><?=$this->format_model->name($user)?></strong>
|
||||||
<span class="dim"><?=lang('joined') . ' ' . $this->format_model->date($user['created'])?></span>
|
<span class="dim"><?=$user['username']?></span>
|
||||||
<span class="dim"><?=lang('seen') . ' ' . $this->format_model->date($user['seen'])?></span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><?=lang('tbl_username')?></td>
|
|
||||||
<td><?=$user['username']?></td>
|
|
||||||
<tr>
|
|
||||||
<tr>
|
|
||||||
<td><?=lang('tbl_email')?></td>
|
|
||||||
<td><?=$user['email']?></td>
|
|
||||||
<tr>
|
|
||||||
<tr>
|
|
||||||
<td><?=lang('tbl_gender')?></td>
|
|
||||||
<td><?=$user['gender']?></td>
|
|
||||||
<tr>
|
|
||||||
<tr>
|
|
||||||
<td><?=lang('tbl_uid')?></td>
|
|
||||||
<td><?=$user['id']?></td>
|
|
||||||
<tr>
|
|
||||||
</table>
|
|
||||||
</a>
|
</a>
|
||||||
<?
|
<?
|
||||||
|
|
3
src/web/_views/apps/people/footer.php
Normal file
3
src/web/_views/apps/people/footer.php
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<?php /* Copyright (c) 2024 Freya Murphy */ ?>
|
||||||
|
<?php /* vi: syntax=php */ ?>
|
||||||
|
</div>
|
6
src/web/_views/apps/people/header.php
Normal file
6
src/web/_views/apps/people/header.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php /* Copyright (c) 2024 Freya Murphy */ ?>
|
||||||
|
<?php /* vi: syntax=php */ ?>
|
||||||
|
<div id="main-content" class="col">
|
||||||
|
<h1 class="title"><?=lang('title')?></h1>
|
||||||
|
<h3 class="desc"><?=lang('desc')?></h3>
|
||||||
|
<hr>
|
|
@ -1,21 +1,19 @@
|
||||||
<?php /* Copyright (c) 2024 Freya Murphy */ ?>
|
<?php /* Copyright (c) 2024 Freya Murphy */ ?>
|
||||||
<?php /* vi: syntax=php */ ?>
|
<?php /* vi: syntax=php */ ?>
|
||||||
<div id="main-content" class="col">
|
<div id="people-container" class="col">
|
||||||
<h1 class="title"><?=lang('title')?></h1>
|
<?php
|
||||||
<h3 class="desc"><?=lang('desc')?></h3>
|
|
||||||
<hr>
|
|
||||||
<div id="people-container" class="col">
|
|
||||||
<?php
|
|
||||||
$pdata = $this->people();
|
$pdata = $this->people();
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
$loaded = count($pdata['users']);
|
$loaded = count($pdata['users']);
|
||||||
$page_size = $pdata['page_size'];
|
$page_size = $pdata['page_size'];
|
||||||
$total = $pdata['count'];
|
$total = $pdata['count'];
|
||||||
$max = $pdata['max_id'];
|
$max = $pdata['max_id'];
|
||||||
?>
|
$filter_uid = $pdata['filter_uid'];
|
||||||
<?php if ($loaded >= $page_size && $page_size < $total): ?>
|
$filer_type = $pdata['filter_type'];
|
||||||
|
?>
|
||||||
|
<?php if ($loaded >= $page_size && $page_size < $total): ?>
|
||||||
<?=ilang('action_load_users',
|
<?=ilang('action_load_users',
|
||||||
id: 'action-load-users',
|
id: 'action-load-users',
|
||||||
class: 'btn btn-line btn-wide mb',
|
class: 'btn btn-line btn-wide mb',
|
||||||
|
@ -23,13 +21,13 @@
|
||||||
'loaded' => $loaded,
|
'loaded' => $loaded,
|
||||||
'pageSize' => $page_size,
|
'pageSize' => $page_size,
|
||||||
'userCount' => $total,
|
'userCount' => $total,
|
||||||
'userMax' => $max
|
'userMax' => $max,
|
||||||
|
'filterUid' => $filter_uid,
|
||||||
|
'filterType' => $filer_type
|
||||||
)
|
)
|
||||||
)?>
|
)?>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var urlParams = new URLSearchParams(window.location.search).toString();
|
|
||||||
|
|
||||||
$('#action-load-users').on('click', function() {
|
$('#action-load-users').on('click', function() {
|
||||||
let me = $(this);
|
let me = $(this);
|
||||||
let page = me.attr('page');
|
let page = me.attr('page');
|
||||||
|
@ -44,7 +42,15 @@
|
||||||
let userCount = Number(me.attr('userCount'));
|
let userCount = Number(me.attr('userCount'));
|
||||||
let userMax = Number(me.attr('userMax'));
|
let userMax = Number(me.attr('userMax'));
|
||||||
|
|
||||||
let url = '/people/people?page=' + page + '&max=' + userMax + '&' + urlParams;
|
let filterType = me.attr('filterType');
|
||||||
|
let filterUid = me.attr('filterUid');
|
||||||
|
|
||||||
|
let url = '/people/people?page=' + page + '&max=' + userMax;
|
||||||
|
|
||||||
|
if (filterType && filterUid) {
|
||||||
|
url += '&filter=' + filterType + '&uid=' + filterUid;
|
||||||
|
}
|
||||||
|
|
||||||
$.get(url, function (data) {
|
$.get(url, function (data) {
|
||||||
if (data === '') {
|
if (data === '') {
|
||||||
me.remove();
|
me.remove();
|
||||||
|
@ -63,5 +69,4 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,37 +1,158 @@
|
||||||
|
<?php /* Copyright (c) 2024 Freya Murphy */ ?>
|
||||||
|
<?php /* vi: syntax=php */ ?>
|
||||||
<div id="main-content">
|
<div id="main-content">
|
||||||
|
<div id="profile-header-container">
|
||||||
<div id="profile-header" class="col">
|
<div id="profile-header" class="col">
|
||||||
<div class="banner image-loading">
|
<?=image('/api/rpc/profile_banner?user_id=' . $user['id'], 'banner')?>
|
||||||
<img src="/api/rpc/profile_banner?user_id=<?=$user['id']?>">
|
|
||||||
</div>
|
|
||||||
<div class="info row">
|
<div class="info row">
|
||||||
<div class="pfp-wrapper">
|
<div class="pfp-wrapper">
|
||||||
<?php $this->view('template/pfp', array('user' => $user)); ?>
|
<?=pfp($user)?>
|
||||||
</div>
|
</div>
|
||||||
<div class="col content">
|
<div class="col content">
|
||||||
<strong class="name"><?=$this->format_model->name($user)?></strong>
|
<strong class="name"><?=$this->format_model->name($user)?></strong>
|
||||||
<span class="dim"><?=lang('joined') . $this->format_model->date($user['created'])?></span>
|
<span class="dim"><?=$user['follower_count'] . ' ' . lang('followers')?></span>
|
||||||
|
<?php if(strlen($user['profile_bio']) > 0): ?>
|
||||||
|
<br>
|
||||||
|
<strong><?=lang('bio')?></strong>
|
||||||
|
<span class="dim"><?=$user['profile_bio']?></span>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row options">
|
<div class="row options">
|
||||||
<?=ilang('action_posts',
|
<?=ilang('action_posts',
|
||||||
sub: [$user['first_name']],
|
sub: [$user['first_name']],
|
||||||
class: 'btn'
|
class: 'btn btn-blue btn-border',
|
||||||
|
id: 'action-posts'
|
||||||
)?>
|
)?>
|
||||||
<?=ilang('action_about',
|
<?=ilang('action_about',
|
||||||
sub: [$user['first_name']],
|
sub: [$user['first_name']],
|
||||||
class: 'btn'
|
class: 'btn',
|
||||||
|
id: 'action-about'
|
||||||
)?>
|
)?>
|
||||||
<?=ilang('action_friends',
|
<?=ilang('action_followers',
|
||||||
sub: [$user['first_name']],
|
sub: [$user['first_name']],
|
||||||
class: 'btn'
|
class: 'btn',
|
||||||
|
id: 'action-followers'
|
||||||
|
)?>
|
||||||
|
<?=ilang('action_following',
|
||||||
|
sub: [$user['first_name']],
|
||||||
|
class: 'btn',
|
||||||
|
id: 'action-following'
|
||||||
)?>
|
)?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="#tab-posts">
|
</div>
|
||||||
|
<div id="tab-posts" class="tab">
|
||||||
<?php
|
<?php
|
||||||
$_GET['user_id'] = $user['id'];
|
$_GET['user_id'] = $user['id'];
|
||||||
$this->post_controller->index();
|
$this->post_controller->index();
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="tab-about" class="tab">
|
||||||
|
<h1><?=lang('about_general')?></h1>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_general_username')?></strong></td>
|
||||||
|
<td><?=$user['username']?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_general_full_name')?></strong></td>
|
||||||
|
<td><?=$user['first_name'] . ' ' . $user['last_name']?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_general_email')?></strong></td>
|
||||||
|
<td><?=$user['email']?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_general_gender')?></strong></td>
|
||||||
|
<td><?=$user['gender']?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_general_birth_date')?></strong></td>
|
||||||
|
<td><?=$user['birth_date']?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<h1><?=lang('about_stats')?></h1>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_stats_posts')?></strong></td>
|
||||||
|
<td><?=$user['post_count']?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_stats_like')?></strong></td>
|
||||||
|
<td><?=$user['like_count']?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_stats_comments')?></strong></td>
|
||||||
|
<td><?=$user['comment_count']?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_stats_following')?></strong></td>
|
||||||
|
<td><?=$user['followed_count']?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_stats_joined')?></strong></td>
|
||||||
|
<td><?=$user['created']?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?=lang('about_stats_seen')?></strong></td>
|
||||||
|
<td><?=$user['seen']?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="tab-followers" class="tab">
|
||||||
|
<?php
|
||||||
|
$_GET['filter'] = 'follower';
|
||||||
|
$_GET['uid'] = $user['id'];
|
||||||
|
$this->people_controller->content();
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<div id="tab-following" class="tab">
|
||||||
|
<?php
|
||||||
|
$_GET['filter'] = 'followee';
|
||||||
|
$_GET['uid'] = $user['id'];
|
||||||
|
$this->people_controller->content();
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let tabs = {};
|
||||||
|
|
||||||
|
const disableTab = (tab) => {
|
||||||
|
tab.btn.removeClass('btn-blue');
|
||||||
|
tab.btn.removeClass('btn-border');
|
||||||
|
tab.tab.css('display', 'none');
|
||||||
|
};
|
||||||
|
|
||||||
|
const enableTab = (tab) => {
|
||||||
|
tab.btn.addClass('btn-blue');
|
||||||
|
tab.btn.addClass('btn-border');
|
||||||
|
tab.tab.css('display', '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadTab = (name, disable = true) => {
|
||||||
|
let btn = $('#action-' + name);
|
||||||
|
btn.on('click', function() {
|
||||||
|
for (let tab of Object.values(tabs)) {
|
||||||
|
disableTab(tab);
|
||||||
|
}
|
||||||
|
enableTab(tabs[name]);
|
||||||
|
});
|
||||||
|
|
||||||
|
tabs[name] = {
|
||||||
|
'btn': btn,
|
||||||
|
'tab': $('#tab-' + name)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (disable) {
|
||||||
|
disableTab(tabs[name]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadTab('posts', false);
|
||||||
|
loadTab('about');
|
||||||
|
loadTab('followers');
|
||||||
|
loadTab('following');
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<div class="nav-center" :class="{hidden: !visible}">
|
<div class="nav-center" :class="{hidden: !visible}">
|
||||||
<a
|
<a
|
||||||
id="action-home"
|
id="action-home"
|
||||||
class="btn"
|
class="btn<?=$this->main->info['app'] == 'home' ? ' btn-blue btn-border' : ''?>"
|
||||||
href="/home"
|
href="/home"
|
||||||
title="<?=lang('action_home_tip')?>"
|
title="<?=lang('action_home_tip')?>"
|
||||||
>
|
>
|
||||||
|
@ -20,35 +20,33 @@
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
id="action-people"
|
id="action-people"
|
||||||
class="btn"
|
class="btn<?=$this->main->info['app'] == 'people' ? ' btn-blue btn-border' : ''?>"
|
||||||
href="/people"
|
href="/people"
|
||||||
title="<?=lang('action_people_tip')?>"
|
title="<?=lang('action_people_tip')?>"
|
||||||
>
|
>
|
||||||
<i class="mi mi-lg">people</i>
|
<i class="mi mi-lg">people</i>
|
||||||
<span><?=lang('action_people_text')?></span>
|
<span><?=lang('action_people_text')?></span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<!--a
|
||||||
id="action-chat"
|
id="action-chat"
|
||||||
class="btn"
|
class="btn<?=$this->main->info['app'] == 'chat' ? ' btn-blue btn-border' : ''?>"
|
||||||
href="/chat"
|
href="/chat"
|
||||||
title="<?=lang('action_chat_tip')?>"
|
title="<?=lang('action_chat_tip')?>"
|
||||||
>
|
>
|
||||||
<i class="mi mi-lg">chat</i>
|
<i class="mi mi-lg">chat</i>
|
||||||
<span><?=lang('action_chat_text')?></span>
|
<span><?=lang('action_chat_text')?></span>
|
||||||
</a>
|
</a-->
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-right">
|
<div class="nav-right">
|
||||||
<button
|
<button
|
||||||
id="action-hamburger"
|
id="action-hamburger"
|
||||||
title="<?=lang('action_hamburger_tip')?>"
|
title="<?=lang('action_hamburger_tip')?>"
|
||||||
|
class="btn mr"
|
||||||
>
|
>
|
||||||
<i class="mi mi-lg">menu</i>
|
<i class="mi mi-lg">menu</i>
|
||||||
</button>
|
</button>
|
||||||
<?php if($self): ?>
|
<?php if($self): ?>
|
||||||
<?php $this->view('template/pfp', array(
|
<?=pfp($self)?>
|
||||||
'user' => $self,
|
|
||||||
'class' => 'pfp-sm ml',
|
|
||||||
)); ?>
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?=ilang('action_login', class: 'btn', href: '/auth/login')?>
|
<?=ilang('action_login', class: 'btn', href: '/auth/login')?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<form id="new-post-form">
|
<form id="new-post-form">
|
||||||
<div class="modal-content new-post-modal">
|
<div class="modal-content new-post-modal">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<?php $this->view('template/pfp', array('user' => $user))?>
|
<?=pfp($user)?>
|
||||||
<div class="col ml">
|
<div class="col ml">
|
||||||
<strong><?=$user['first_name'] . ' ' . $user['last_name']?></strong>
|
<strong><?=$user['first_name'] . ' ' . $user['last_name']?></strong>
|
||||||
<span class="dim"><?=lang('now')?></span>
|
<span class="dim"><?=lang('now')?></span>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
$format_model = $this->load->model('format');
|
$format_model = $this->load->model('format');
|
||||||
?>
|
?>
|
||||||
<div class="comment row mt">
|
<div class="comment row mt">
|
||||||
<?php $this->view('template/pfp', array('user' => $user))?>
|
<?=pfp($user)?>
|
||||||
<div class="ml col sub-card">
|
<div class="ml col sub-card">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<strong><?=$format_model->name($user)?></strong>
|
<strong><?=$format_model->name($user)?></strong>
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
<?php /* Copyright (c) 2024 Freya Murphy */ ?>
|
|
||||||
<?php /* vi: syntax=php */ ?>
|
|
||||||
<?php
|
|
||||||
$class = isset($class) ? $class : '';
|
|
||||||
$link = isset($link) ? $link : TRUE;
|
|
||||||
?>
|
|
||||||
<?php if($link): ?>
|
|
||||||
<a class="image-loading pfp <?=$class?>" href="/profile?id=<?=$user['id']?>">
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="image-loading pfp <?=$class?>">
|
|
||||||
<?php endif; ?>
|
|
||||||
<img src="/api/rpc/profile_avatar?user_id=<?=$user['id']?>"/>
|
|
||||||
<?php if ($link): ?>
|
|
||||||
</a>
|
|
||||||
<?php else: ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
|
@ -2,7 +2,7 @@
|
||||||
<?php /* vi: syntax=php */ ?>
|
<?php /* vi: syntax=php */ ?>
|
||||||
<div class="post card">
|
<div class="post card">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<?php $this->view('template/pfp', array('user' => $user))?>
|
<?=pfp($user)?>
|
||||||
<div class="col ml">
|
<div class="col ml">
|
||||||
<strong><?=$user['first_name'] . ' ' . $user['last_name']?></strong>
|
<strong><?=$user['first_name'] . ' ' . $user['last_name']?></strong>
|
||||||
<span class="dim"><?=$post['created']?></span>
|
<span class="dim"><?=$post['created']?></span>
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
$post_attrs['likeId'] = $post['like_id'];
|
$post_attrs['likeId'] = $post['like_id'];
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
<span class="likes dim"><span class="count"><?=$post['like_count']?></span><?=' ' . lang('likes')?></span>
|
||||||
<?php if ($self): ?>
|
<?php if ($self): ?>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -61,7 +62,7 @@
|
||||||
</div>
|
</div>
|
||||||
<?php if ($self): ?>
|
<?php if ($self): ?>
|
||||||
<div class="row pb">
|
<div class="row pb">
|
||||||
<?php $this->view('template/pfp', array('user' => $self))?>
|
<?=pfp($self)?>
|
||||||
<form class="ml action-new-comment-form row">
|
<form class="ml action-new-comment-form row">
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
'pageSize' => $page_size,
|
'pageSize' => $page_size,
|
||||||
'postCount' => $total,
|
'postCount' => $total,
|
||||||
'postMax' => $max,
|
'postMax' => $max,
|
||||||
'userId' => $filterUid
|
'userId' => $filterUid ? json_encode($filterUid) : ''
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ class Aesthetic {
|
||||||
],
|
],
|
||||||
'css' => [
|
'css' => [
|
||||||
'css/profile.css',
|
'css/profile.css',
|
||||||
|
'css/people.css',
|
||||||
'css/post.css'
|
'css/post.css'
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
33
src/web/helper/image.php
Normal file
33
src/web/helper/image.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php /* Copyright (c) 2024 Freya Murphy */
|
||||||
|
|
||||||
|
function image($src, $class = NULL, $link = NULL): string {
|
||||||
|
if ($class) {
|
||||||
|
$class = 'image-loading ' . $class;
|
||||||
|
} else {
|
||||||
|
$class = 'image-loading';
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = '';
|
||||||
|
|
||||||
|
if ($link) {
|
||||||
|
$content .= '<a class="' . $class . '" href="' . $link . '">';
|
||||||
|
} else {
|
||||||
|
$content .= '<span class="' . $class . '">';
|
||||||
|
}
|
||||||
|
$content .= '<img src="' . $src . '" onerror="onImgError(this)" onload="onImgLoad(this)"/>';
|
||||||
|
if ($link) {
|
||||||
|
$content .= '</a>';
|
||||||
|
} else {
|
||||||
|
$content .= '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pfp(
|
||||||
|
$user,
|
||||||
|
$embedLink = TRUE,
|
||||||
|
): string {
|
||||||
|
$link = $embedLink ? '/profile?id=' . $user['id'] : NULL;
|
||||||
|
return image('/api/rpc/profile_avatar?user_id=' . $user['id'], 'pfp', link: $link);
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ session_start();
|
||||||
$webroot = dirname(__FILE__);
|
$webroot = dirname(__FILE__);
|
||||||
|
|
||||||
// load all the helper files
|
// load all the helper files
|
||||||
|
require($webroot . '/helper/image.php');
|
||||||
require($webroot . '/helper/error.php');
|
require($webroot . '/helper/error.php');
|
||||||
require($webroot . '/helper/lang.php');
|
require($webroot . '/helper/lang.php');
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,34 @@ $lang['title'] = '%s\'s profile';
|
||||||
|
|
||||||
$lang['joined'] = 'Joined: ';
|
$lang['joined'] = 'Joined: ';
|
||||||
$lang['seen'] = 'Seen: ';
|
$lang['seen'] = 'Seen: ';
|
||||||
|
$lang['followers'] = 'Followers';
|
||||||
|
$lang['bio'] = 'Bio';
|
||||||
|
|
||||||
$lang['action_posts_text'] = 'Posts';
|
$lang['action_posts_text'] = 'Posts';
|
||||||
$lang['action_posts_tip'] = 'View %s\'s posts';
|
$lang['action_posts_tip'] = 'View %s\'s posts';
|
||||||
$lang['action_about_text'] = 'About';
|
$lang['action_about_text'] = 'About';
|
||||||
$lang['action_about_tip'] = 'View %s\'s information';
|
$lang['action_about_tip'] = 'View %s\'s information';
|
||||||
$lang['action_friends_text'] = 'Friends';
|
$lang['action_followers_text'] = 'Followers';
|
||||||
$lang['action_friends_tip'] = 'View %s\'s friends';
|
$lang['action_followers_tip'] = 'View %s\'s followres';
|
||||||
|
$lang['action_following_text'] = 'Following';
|
||||||
|
$lang['action_following_tip'] = 'View who %s is following';
|
||||||
|
|
||||||
$lang['action_load_posts_text'] = 'Load more posts';
|
$lang['action_load_posts_text'] = 'Load more posts';
|
||||||
$lang['action_load_posts_tip'] = 'Load more posts';
|
$lang['action_load_posts_tip'] = 'Load more posts';
|
||||||
|
|
||||||
|
$lang['about_general'] = 'General';
|
||||||
|
$lang['about_general_username'] = 'Username';
|
||||||
|
$lang['about_general_full_name'] = 'Full Name';
|
||||||
|
$lang['about_general_email'] = 'Email';
|
||||||
|
$lang['about_general_gender'] = 'Gender';
|
||||||
|
$lang['about_general_birth_date'] = 'Birthday';
|
||||||
|
|
||||||
|
$lang['about_stats'] = 'Statistics';
|
||||||
|
$lang['about_stats_posts'] = 'Posts Created';
|
||||||
|
$lang['about_stats_like'] = 'Posts Liked';
|
||||||
|
$lang['about_stats_comments'] = 'Comments Created';
|
||||||
|
$lang['about_stats_following'] = 'Accounts Followed';
|
||||||
|
$lang['about_stats_joined'] = 'Date Joined';
|
||||||
|
$lang['about_stats_seen'] = 'Last Seen';
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -46,5 +46,6 @@ $lang['action_new_post_tip'] = 'Author a new post.';
|
||||||
|
|
||||||
// Words
|
// Words
|
||||||
$lang['now'] = 'Now';
|
$lang['now'] = 'Now';
|
||||||
|
$lang['likes'] = 'Likes';
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
Loading…
Reference in a new issue