Compare commits
80 commits
20230831.4
...
master
Author | SHA1 | Date | |
---|---|---|---|
flash | 5d3e1d4960 | ||
flash | 9bb943bacf | ||
flash | 107d16cf46 | ||
flash | 0afc5186a7 | ||
flash | 0300bae994 | ||
flash | cb0c64f8ed | ||
flash | 89ef9d9ad1 | ||
flash | c02d922dc6 | ||
flash | 80cd6222c4 | ||
flash | 344a3c9160 | ||
flash | df5dbdf3ad | ||
flash | c0caceed7b | ||
flash | be54ce2c22 | ||
flash | 070dc5e782 | ||
flash | b89621cb1a | ||
flash | 760cca0e5d | ||
flash | fe77f1616c | ||
flash | eb81ed7a82 | ||
flash | 8ef11afe02 | ||
flash | cca016ba10 | ||
flash | b80151583e | ||
flash | d8cc208a85 | ||
flash | 4b2f9a2fec | ||
flash | ddb255bf32 | ||
flash | 5a70e3f3f1 | ||
flash | bd3e055323 | ||
flash | dba5754ccc | ||
flash | ec6ba3f781 | ||
flash | 70ec285f99 | ||
flash | 77eadd5bde | ||
flash | f0fc735975 | ||
flash | adb80bad9e | ||
flash | f30cf41f86 | ||
flash | b4f5dd0660 | ||
flash | 133e2f420c | ||
flash | bf65c95490 | ||
flash | 7ef5994da4 | ||
flash | 2b34bde413 | ||
flash | 432615508d | ||
flash | a4cc14e4c1 | ||
flash | 65e695e9d9 | ||
flash | 2e6a84b46d | ||
flash | 8f56174637 | ||
flash | 19fbe59ddd | ||
flash | f7a571e551 | ||
flash | 5f57e3fdf4 | ||
flash | c2836719c7 | ||
flash | 14c9a1d9f6 | ||
flash | 4f1e35b566 | ||
flash | 9aa2a1431e | ||
flash | 4322f2561c | ||
flash | 67d9620037 | ||
flash | 904d220582 | ||
flash | d9b152fb78 | ||
flash | a945cc518a | ||
flash | edc64b45ff | ||
flash | 17e0d1f591 | ||
flash | 5554c5c28d | ||
flash | 55e23c7b5d | ||
flash | e376671136 | ||
flash | 3e49f6e503 | ||
flash | 7db43a2acd | ||
flash | 099bd899ed | ||
flash | 1248c0d2f6 | ||
flash | c3bed1c0e3 | ||
flash | 163da8b213 | ||
flash | c68279add9 | ||
flash | 737c99280e | ||
flash | 8b0f960c86 | ||
flash | c5a284f360 | ||
flash | 506d32d210 | ||
flash | 498ec0cf9a | ||
flash | 15e96684c2 | ||
flash | 73e4597e16 | ||
flash | 9b2c409a24 | ||
flash | 7190a5f4df | ||
flash | 5c67d49225 | ||
flash | 69e4d05be6 | ||
flash | 2d0f083e1a | ||
flash | 1da6470928 |
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -11,6 +11,8 @@
|
||||||
/composer.local.json
|
/composer.local.json
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
/config/config.cfg
|
||||||
|
/config/github.cfg
|
||||||
/config/config.ini
|
/config/config.ini
|
||||||
/config/github.ini
|
/config/github.ini
|
||||||
/.debug
|
/.debug
|
||||||
|
@ -43,3 +45,7 @@
|
||||||
|
|
||||||
# Google
|
# Google
|
||||||
/public/robots.txt
|
/public/robots.txt
|
||||||
|
|
||||||
|
# moguu?
|
||||||
|
/public/moguu.swf
|
||||||
|
/public/moguu.html
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2017-2023, flashwave <me@flash.moe>
|
Copyright (c) 2017-2024, flashwave <me@flash.moe>
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
|
@ -17,15 +17,10 @@ exports.process = async function(root, options) {
|
||||||
return '';
|
return '';
|
||||||
included.push(fullPath);
|
included.push(fullPath);
|
||||||
|
|
||||||
if(!fullPath.startsWith(root)) {
|
if(!fullPath.startsWith(root))
|
||||||
console.error('INVALID PATH: ' + fullPath);
|
|
||||||
return '/* *** INVALID PATH: ' + fullPath + ' */';
|
return '/* *** INVALID PATH: ' + fullPath + ' */';
|
||||||
}
|
if(!fs.existsSync(fullPath))
|
||||||
|
|
||||||
if(!fs.existsSync(fullPath)) {
|
|
||||||
console.error('FILE NOT FOUND: ' + fullPath);
|
|
||||||
return '/* *** FILE NOT FOUND: ' + fullPath + ' */';
|
return '/* *** FILE NOT FOUND: ' + fullPath + ' */';
|
||||||
}
|
|
||||||
|
|
||||||
const lines = readline.createInterface({
|
const lines = readline.createInterface({
|
||||||
input: fs.createReadStream(fullPath),
|
input: fs.createReadStream(fullPath),
|
||||||
|
@ -58,6 +53,19 @@ exports.process = async function(root, options) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'buildvars':
|
||||||
|
if(typeof options.buildVars === 'object') {
|
||||||
|
const bvTarget = options.buildVarsTarget || 'window';
|
||||||
|
const bvProps = [];
|
||||||
|
|
||||||
|
for(const bvName in options.buildVars)
|
||||||
|
bvProps.push(`${bvName}: { value: ${JSON.stringify(options.buildVars[bvName])}, writable: false }`);
|
||||||
|
|
||||||
|
if(Object.keys(bvProps).length > 0)
|
||||||
|
output += `Object.defineProperties(${bvTarget}, { ${bvProps.join(', ')} });\n`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
output += line;
|
output += line;
|
||||||
output += "\n";
|
output += "\n";
|
||||||
|
@ -84,7 +92,7 @@ exports.housekeep = function(assetsPath) {
|
||||||
};
|
};
|
||||||
}).sort((a, b) => b.lastMod - a.lastMod).map(info => info.name);
|
}).sort((a, b) => b.lastMod - a.lastMod).map(info => info.name);
|
||||||
|
|
||||||
const regex = /^(.+)-([a-f0-9]+)\.(.+)$/i;
|
const regex = /^(.+)[\-\.]([a-f0-9]+)\.(.+)$/i;
|
||||||
const counts = {};
|
const counts = {};
|
||||||
|
|
||||||
for(const fileName of files) {
|
for(const fileName of files) {
|
||||||
|
|
|
@ -154,6 +154,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.forum__post__action {
|
.forum__post__action {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
display: block;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|
|
@ -146,9 +146,14 @@
|
||||||
}
|
}
|
||||||
.header__desktop__user__button__count {
|
.header__desktop__user__button__count {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 1px;
|
top: -5px;
|
||||||
right: 1px;
|
right: -3px;
|
||||||
font-size: 10px;
|
z-index: 1;
|
||||||
|
font-size: .5em;
|
||||||
|
line-height: 1.4em;
|
||||||
|
text-align: right;
|
||||||
|
padding: 2px 2px 0;
|
||||||
|
border-radius: 4px;
|
||||||
background-color: var(--header-accent-colour);
|
background-color: var(--header-accent-colour);
|
||||||
opacity: .9;
|
opacity: .9;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
92
assets/misuzu.css/hljs.css
Normal file
92
assets/misuzu.css/hljs.css
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
pre code.hljs {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 1em;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
}
|
||||||
|
|
||||||
|
code.hljs {
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
color: #eee;
|
||||||
|
background: #121212;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong,
|
||||||
|
.hljs-emphasis,
|
||||||
|
.hljs-section {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-quote,
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-literal {
|
||||||
|
color: #b2b376;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-code {
|
||||||
|
background-color: #242424;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-emphasis,
|
||||||
|
.hljs-stronge,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-attribute,
|
||||||
|
.hljs-params {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag,
|
||||||
|
.hljs-section,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-name {
|
||||||
|
color: #9475b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-subst,
|
||||||
|
.hljs-tag,
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-selector-attr {
|
||||||
|
color: #c8b9d7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-class .hljs-title,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-pseudo {
|
||||||
|
color: #b37fae;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-string {
|
||||||
|
color: #76b38a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-template-tag,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-link {
|
||||||
|
color: #b39a76;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-meta {
|
||||||
|
color: #70647b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-addition {
|
||||||
|
background: #0e4d0e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-deletion {
|
||||||
|
background: #4d0e0e;
|
||||||
|
}
|
|
@ -3,7 +3,6 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
position: relative;
|
||||||
outline-style: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
|
@ -57,6 +56,8 @@ body {
|
||||||
|
|
||||||
html {
|
html {
|
||||||
scrollbar-color: var(--accent-colour) var(--background-colour);
|
scrollbar-color: var(--accent-colour) var(--background-colour);
|
||||||
|
accent-color: var(--accent-colour);
|
||||||
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
|
@ -117,6 +118,8 @@ html {
|
||||||
@include permissions.css;
|
@include permissions.css;
|
||||||
@include warning.css;
|
@include warning.css;
|
||||||
|
|
||||||
|
@include hljs.css;
|
||||||
|
|
||||||
@include _input/button.css;
|
@include _input/button.css;
|
||||||
@include _input/checkbox.css;
|
@include _input/checkbox.css;
|
||||||
@include _input/colour.css;
|
@include _input/colour.css;
|
||||||
|
@ -161,6 +164,8 @@ html {
|
||||||
|
|
||||||
@include manage/_manage.css;
|
@include manage/_manage.css;
|
||||||
|
|
||||||
|
@include messages/messages.css;
|
||||||
|
|
||||||
@include news/container.css;
|
@include news/container.css;
|
||||||
@include news/feeds.css;
|
@include news/feeds.css;
|
||||||
@include news/list.css;
|
@include news/list.css;
|
||||||
|
|
|
@ -53,7 +53,6 @@
|
||||||
.markdown code {
|
.markdown code {
|
||||||
padding: .2em .4em;
|
padding: .2em .4em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: rgba(0, 0, 0, .7);
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
.markdown del code { text-decoration: inherit; }
|
.markdown del code { text-decoration: inherit; }
|
||||||
|
@ -65,7 +64,6 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,4 +17,5 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
37
assets/misuzu.css/messages/actions.css
Normal file
37
assets/misuzu.css/messages/actions.css
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
.messages-actions-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 30px;
|
||||||
|
margin: 1px;
|
||||||
|
font-size: 1.3em;
|
||||||
|
line-height: 1.4em;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color .1s;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
background-color: inherit;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.messages-actions-item:hover,
|
||||||
|
.messages-actions-item:focus {
|
||||||
|
background-color: #444f;
|
||||||
|
}
|
||||||
|
.messages-actions-item:active,
|
||||||
|
.messages-actions-item-current {
|
||||||
|
background-color: var(--accent-colour) !important;
|
||||||
|
}
|
||||||
|
.messages-actions-item[disabled] {
|
||||||
|
background-color: inherit !important;
|
||||||
|
opacity: .4;
|
||||||
|
}
|
||||||
|
.messages-actions-item-icon {
|
||||||
|
text-align: center;
|
||||||
|
width: 30px;
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.messages-actions-item-label {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
26
assets/misuzu.css/messages/columns.css
Normal file
26
assets/misuzu.css/messages/columns.css
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
.messages-columns {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-columns-sidebar {
|
||||||
|
width: 200px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-columns-content {
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.messages-columns {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-columns-sidebar {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
80
assets/misuzu.css/messages/entry.css
Normal file
80
assets/misuzu.css/messages/entry.css
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
.messages-entry {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 2px 4px;
|
||||||
|
gap: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.messages-entry-header {
|
||||||
|
display: flex;
|
||||||
|
font-size: 1.1em;
|
||||||
|
line-height: 1.6em;
|
||||||
|
border-bottom: 2px solid #9999;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.messages-entry-check {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.messages-entry-check input {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.messages-entry-unread {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.messages-entry-unread-orb {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: var(--accent-colour);
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
.messages-entry-author {
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid var(--user-colour, currentColor);
|
||||||
|
margin: 0 0 -2px;
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.messages-entry-spacing {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
.messages-entry-datetime {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #aaa;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
.messages-entry-subject {
|
||||||
|
line-height: 1.4em;
|
||||||
|
color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.messages-entry-preview {
|
||||||
|
line-height: 1.4em;
|
||||||
|
color: #888;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.messages-entry-preview .messages-entry-overflow {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
.messages-entry-overflow {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
33
assets/misuzu.css/messages/folder.css
Normal file
33
assets/misuzu.css/messages/folder.css
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
.messages-folder {
|
||||||
|
margin: 1px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
.messages-folder-item {
|
||||||
|
background-color: #161616;
|
||||||
|
transition: background-color .1s;
|
||||||
|
}
|
||||||
|
.messages-folder-item:nth-child(2n) {
|
||||||
|
background-color: #1f1f1f;
|
||||||
|
}
|
||||||
|
.messages-folder-item:hover,
|
||||||
|
.messages-folder-item:focus {
|
||||||
|
background-color: #262626;
|
||||||
|
}
|
||||||
|
.messages-folder-item:active,
|
||||||
|
.messages-folder-item-current {
|
||||||
|
background-color: var(--accent-colour) !important;
|
||||||
|
}
|
||||||
|
.messages-folder-notice {
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
.messages-folder-notice-text {
|
||||||
|
font-size: 1.4em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
.messages-folder .pagination {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
135
assets/misuzu.css/messages/message.css
Normal file
135
assets/misuzu.css/messages/message.css
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
.messages-message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.messages-message-snippet {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: .9em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
color: #888;
|
||||||
|
gap: 5px;
|
||||||
|
opacity: .8;
|
||||||
|
transition: opacity .1s;
|
||||||
|
}
|
||||||
|
.messages-message-snippet:hover,
|
||||||
|
.messages-message-snippet:focus,
|
||||||
|
.messages-message-snippet:focus-within {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.messages-message-draft {
|
||||||
|
border-top: 2px solid var(--accent-colour) !important;
|
||||||
|
border-left: 2px solid var(--accent-colour) !important;
|
||||||
|
border-right: 2px solid var(--accent-colour);
|
||||||
|
border-bottom: 2px solid var(--accent-colour);
|
||||||
|
}
|
||||||
|
.messages-message-deleted {
|
||||||
|
border-top: 2px solid red;
|
||||||
|
border-left: 2px solid red;
|
||||||
|
border-right: 2px solid red !important;
|
||||||
|
border-bottom: 2px solid red !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-message-overflow {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-message-header {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
border-bottom: 1px #444 solid;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-message-sender-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.messages-message-sender-avatar img {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-message-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.messages-message-details-spacing {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-message-header-columns {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.messages-message-sender-name {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.messages-message-sender-name a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 700;
|
||||||
|
border-bottom: 2px solid var(--user-colour, currentColor);
|
||||||
|
}
|
||||||
|
.messages-message-datetime {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
align-self: flex-end;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-message-addressee {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.messages-message-addressee-to {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
.messages-message-addressee-user {
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.messages-message-addressee-user a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 700;
|
||||||
|
border-bottom: 2px solid var(--user-colour, currentColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-message-subject {
|
||||||
|
line-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-message-body {
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
|
.messages-message-body p:first-child {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
.messages-message-body p:last-child {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-message-snippet-body {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
9
assets/misuzu.css/messages/messages.css
Normal file
9
assets/misuzu.css/messages/messages.css
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
@include messages/actions.css;
|
||||||
|
@include messages/columns.css;
|
||||||
|
@include messages/entry.css;
|
||||||
|
@include messages/folder.css;
|
||||||
|
@include messages/message.css;
|
||||||
|
@include messages/recipient.css;
|
||||||
|
@include messages/reply.css;
|
||||||
|
@include messages/sidebar.css;
|
||||||
|
@include messages/thread.css;
|
17
assets/misuzu.css/messages/recipient.css
Normal file
17
assets/misuzu.css/messages/recipient.css
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
.messages-recipient {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-recipient-avatar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-recipient-name {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.messages-recipient-name-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
52
assets/misuzu.css/messages/reply.css
Normal file
52
assets/misuzu.css/messages/reply.css
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
.messages-reply-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-reply-subject-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-reply-body-input {
|
||||||
|
min-width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
.messages-reply-compose .messages-reply-body-input {
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-reply-actions {
|
||||||
|
display: flex;
|
||||||
|
padding: 1px;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
.messages-reply-action {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
display: block;
|
||||||
|
padding: 5px 10px;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color .2s;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.messages-reply-action:hover,
|
||||||
|
.messages-reply-action:focus {
|
||||||
|
background-color: rgba(0, 0, 0, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-reply-options {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.messages-reply-settings {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
11
assets/misuzu.css/messages/sidebar.css
Normal file
11
assets/misuzu.css/messages/sidebar.css
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.messages-sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.messages-sidebar-button {
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
5
assets/misuzu.css/messages/thread.css
Normal file
5
assets/misuzu.css/messages/thread.css
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.messages-thread {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
40
assets/misuzu.js/csrfp.js
Normal file
40
assets/misuzu.js/csrfp.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#include utility.js
|
||||||
|
|
||||||
|
const MszCSRFP = (() => {
|
||||||
|
let elem;
|
||||||
|
const getElement = () => {
|
||||||
|
if(elem === undefined)
|
||||||
|
elem = $q('meta[name="csrfp-token"]');
|
||||||
|
return elem;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getToken = () => {
|
||||||
|
const elem = getElement();
|
||||||
|
return typeof elem.content === 'string' ? elem.content : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const setToken = token => {
|
||||||
|
if(typeof token !== 'string')
|
||||||
|
throw 'token must be a string';
|
||||||
|
|
||||||
|
const elem = getElement();
|
||||||
|
if(typeof elem.content === 'string')
|
||||||
|
elem.content = token;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getToken: getToken,
|
||||||
|
setToken: setToken,
|
||||||
|
setFromHeaders: result => {
|
||||||
|
if(typeof result.headers !== 'function')
|
||||||
|
throw 'result.headers is not a function';
|
||||||
|
|
||||||
|
const headers = result.headers();
|
||||||
|
if(!(headers instanceof Map))
|
||||||
|
throw 'result of result.headers does not return a map';
|
||||||
|
|
||||||
|
if(headers.has('x-csrfp-token'))
|
||||||
|
setToken(headers.get('x-csrfp-token'));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})();
|
|
@ -1,128 +0,0 @@
|
||||||
#include utils.js
|
|
||||||
#include uiharu.js
|
|
||||||
#include aembed.js
|
|
||||||
#include iembed.js
|
|
||||||
#include vembed.js
|
|
||||||
|
|
||||||
var MszEmbed = (function() {
|
|
||||||
let uiharu = undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
init: function(endPoint) {
|
|
||||||
uiharu = new Uiharu(endPoint);
|
|
||||||
},
|
|
||||||
handle: function(targets) {
|
|
||||||
if(!Array.isArray(targets))
|
|
||||||
targets = Array.from(targets);
|
|
||||||
|
|
||||||
const filtered = new Map;
|
|
||||||
for(const target of targets) {
|
|
||||||
if(!(target instanceof HTMLElement)
|
|
||||||
|| !('dataset' in target)
|
|
||||||
|| !('mszEmbedUrl' in target.dataset))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const cleanUrl = target.dataset.mszEmbedUrl.replace(/ /, '%20');
|
|
||||||
if(cleanUrl.indexOf('https://') !== 0
|
|
||||||
&& cleanUrl.indexOf('http://') !== 0
|
|
||||||
&& cleanUrl.indexOf('//') !== 0) {
|
|
||||||
target.textContent = target.dataset.mszEmbedUrl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rc(target);
|
|
||||||
target.appendChild($e({
|
|
||||||
tag: 'i',
|
|
||||||
attrs: {
|
|
||||||
className: 'fas fa-2x fa-spinner fa-pulse',
|
|
||||||
style: {
|
|
||||||
width: '32px',
|
|
||||||
height: '32px',
|
|
||||||
lineHeight: '32px',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
if(filtered.has(cleanUrl))
|
|
||||||
filtered.get(cleanUrl).push(target);
|
|
||||||
else
|
|
||||||
filtered.set(cleanUrl, [target]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const replaceWithUrl = function(targets, url) {
|
|
||||||
for(const target of targets) {
|
|
||||||
let body = $e({
|
|
||||||
tag: 'a',
|
|
||||||
attrs: {
|
|
||||||
className: 'link',
|
|
||||||
href: url,
|
|
||||||
target: '_blank',
|
|
||||||
rel: 'noopener noreferrer',
|
|
||||||
},
|
|
||||||
child: url
|
|
||||||
});
|
|
||||||
$ib(target, body);
|
|
||||||
$r(target);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
filtered.forEach(function(targets, url) {
|
|
||||||
uiharu.lookupOne(url, function(metadata) {
|
|
||||||
if(metadata.error) {
|
|
||||||
replaceWithUrl(targets, url);
|
|
||||||
console.error(metadata.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(metadata.title === undefined) {
|
|
||||||
replaceWithUrl(targets, url);
|
|
||||||
console.warn('Media is no longer available.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let phc = undefined,
|
|
||||||
options = {
|
|
||||||
onembed: console.log,
|
|
||||||
};
|
|
||||||
|
|
||||||
if(metadata.type === 'youtube:video') {
|
|
||||||
phc = MszVideoEmbedPlaceholder;
|
|
||||||
options.type = 'youtube';
|
|
||||||
options.player = MszVideoEmbedYouTube;
|
|
||||||
} else if(metadata.type === 'niconico:video') {
|
|
||||||
phc = MszVideoEmbedPlaceholder;
|
|
||||||
options.type = 'nicovideo';
|
|
||||||
options.player = MszVideoEmbedNicoNico;
|
|
||||||
} else if(metadata.is_video) {
|
|
||||||
phc = MszVideoEmbedPlaceholder;
|
|
||||||
options.type = 'external';
|
|
||||||
options.player = MszVideoEmbedPlayer;
|
|
||||||
//options.frame = MszVideoEmbedFrame;
|
|
||||||
options.nativeControls = true;
|
|
||||||
options.autosize = false;
|
|
||||||
options.maxWidth = 640;
|
|
||||||
options.maxHeight = 360;
|
|
||||||
} else if(metadata.is_audio) {
|
|
||||||
phc = MszAudioEmbedPlaceholder;
|
|
||||||
options.type = 'external';
|
|
||||||
options.player = MszAudioEmbedPlayer;
|
|
||||||
options.nativeControls = true;
|
|
||||||
} else if(metadata.is_image) {
|
|
||||||
phc = MszImageEmbed;
|
|
||||||
options.type = 'external';
|
|
||||||
}
|
|
||||||
|
|
||||||
if(phc === undefined)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for(const target of targets) {
|
|
||||||
const placeholder = new phc(metadata, options, target);
|
|
||||||
if(placeholder !== undefined)
|
|
||||||
placeholder.replaceElement(target);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include utils.js
|
#include utility.js
|
||||||
#include watcher.js
|
#include watcher.js
|
||||||
|
|
||||||
const MszAudioEmbedPlayerEvents = function() {
|
const MszAudioEmbedPlayerEvents = function() {
|
||||||
|
@ -56,7 +56,7 @@ const MszAudioEmbedPlayer = function(metadata, options) {
|
||||||
if(haveNativeControls)
|
if(haveNativeControls)
|
||||||
playerAttrs.controls = 'controls';
|
playerAttrs.controls = 'controls';
|
||||||
|
|
||||||
const watchers = new MszWatcherCollection;
|
const watchers = new MszWatchers;
|
||||||
watchers.define(MszAudioEmbedPlayerEvents());
|
watchers.define(MszAudioEmbedPlayerEvents());
|
||||||
|
|
||||||
const player = $e({
|
const player = $e({
|
||||||
|
@ -84,7 +84,8 @@ const MszAudioEmbedPlayer = function(metadata, options) {
|
||||||
getType: function() { return 'external'; },
|
getType: function() { return 'external'; },
|
||||||
};
|
};
|
||||||
|
|
||||||
watchers.proxy(pub);
|
pub.watch = (name, handler) => watchers.watch(name, handler);
|
||||||
|
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
|
||||||
|
|
||||||
player.addEventListener('play', function() { watchers.call('play', pub); });
|
player.addEventListener('play', function() { watchers.call('play', pub); });
|
||||||
|
|
135
assets/misuzu.js/embed/embed.js
Normal file
135
assets/misuzu.js/embed/embed.js
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
#include utility.js
|
||||||
|
#include embed/audio.js
|
||||||
|
#include embed/image.js
|
||||||
|
#include embed/video.js
|
||||||
|
#include ext/uiharu.js
|
||||||
|
|
||||||
|
const MszEmbed = (function() {
|
||||||
|
let uiharu = undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: function(endPoint) {
|
||||||
|
uiharu = new MszUiharu(endPoint);
|
||||||
|
},
|
||||||
|
handle: function(targets) {
|
||||||
|
if(!Array.isArray(targets))
|
||||||
|
targets = Array.from(targets);
|
||||||
|
|
||||||
|
const filtered = new Map;
|
||||||
|
for(const target of targets) {
|
||||||
|
if(!(target instanceof HTMLElement)
|
||||||
|
|| !('dataset' in target)
|
||||||
|
|| !('mszEmbedUrl' in target.dataset))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const cleanUrl = target.dataset.mszEmbedUrl.replace(/ /, '%20');
|
||||||
|
if(cleanUrl.indexOf('https://') !== 0
|
||||||
|
&& cleanUrl.indexOf('http://') !== 0
|
||||||
|
&& cleanUrl.indexOf('//') !== 0) {
|
||||||
|
target.textContent = target.dataset.mszEmbedUrl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rc(target);
|
||||||
|
target.appendChild($e({
|
||||||
|
tag: 'i',
|
||||||
|
attrs: {
|
||||||
|
className: 'fas fa-2x fa-spinner fa-pulse',
|
||||||
|
style: {
|
||||||
|
width: '32px',
|
||||||
|
height: '32px',
|
||||||
|
lineHeight: '32px',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
if(filtered.has(cleanUrl))
|
||||||
|
filtered.get(cleanUrl).push(target);
|
||||||
|
else
|
||||||
|
filtered.set(cleanUrl, [target]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const replaceWithUrl = function(targets, url) {
|
||||||
|
for(const target of targets) {
|
||||||
|
let body = $e({
|
||||||
|
tag: 'a',
|
||||||
|
attrs: {
|
||||||
|
className: 'link',
|
||||||
|
href: url,
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noopener noreferrer',
|
||||||
|
},
|
||||||
|
child: url
|
||||||
|
});
|
||||||
|
$ib(target, body);
|
||||||
|
$r(target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
filtered.forEach(function(targets, url) {
|
||||||
|
uiharu.lookupOne(url)
|
||||||
|
.catch(ex => {
|
||||||
|
replaceWithUrl(targets, url);
|
||||||
|
console.error(ex);
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
const metadata = result.body();
|
||||||
|
|
||||||
|
if(metadata.error) {
|
||||||
|
replaceWithUrl(targets, url);
|
||||||
|
console.error(metadata.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(metadata.title === undefined) {
|
||||||
|
replaceWithUrl(targets, url);
|
||||||
|
console.warn('Media is no longer available.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let phc = undefined,
|
||||||
|
options = {
|
||||||
|
onembed: console.log,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(metadata.type === 'youtube:video') {
|
||||||
|
phc = MszVideoEmbedPlaceholder;
|
||||||
|
options.type = 'youtube';
|
||||||
|
options.player = MszVideoEmbedYouTube;
|
||||||
|
} else if(metadata.type === 'niconico:video') {
|
||||||
|
phc = MszVideoEmbedPlaceholder;
|
||||||
|
options.type = 'nicovideo';
|
||||||
|
options.player = MszVideoEmbedNicoNico;
|
||||||
|
} else if(metadata.is_video) {
|
||||||
|
phc = MszVideoEmbedPlaceholder;
|
||||||
|
options.type = 'external';
|
||||||
|
options.player = MszVideoEmbedPlayer;
|
||||||
|
//options.frame = MszVideoEmbedFrame;
|
||||||
|
options.nativeControls = true;
|
||||||
|
options.autosize = false;
|
||||||
|
options.maxWidth = 640;
|
||||||
|
options.maxHeight = 360;
|
||||||
|
} else if(metadata.is_audio) {
|
||||||
|
phc = MszAudioEmbedPlaceholder;
|
||||||
|
options.type = 'external';
|
||||||
|
options.player = MszAudioEmbedPlayer;
|
||||||
|
options.nativeControls = true;
|
||||||
|
} else if(metadata.is_image) {
|
||||||
|
phc = MszImageEmbed;
|
||||||
|
options.type = 'external';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(phc === undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for(const target of targets) {
|
||||||
|
const placeholder = new phc(metadata, options, target);
|
||||||
|
if(placeholder !== undefined)
|
||||||
|
placeholder.replaceElement(target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})();
|
|
@ -1,4 +1,4 @@
|
||||||
#include utils.js
|
#include utility.js
|
||||||
|
|
||||||
const MszImageEmbed = function(metadata, options, target) {
|
const MszImageEmbed = function(metadata, options, target) {
|
||||||
options = options || {};
|
options = options || {};
|
|
@ -1,5 +1,5 @@
|
||||||
#include utils.js
|
#include utility.js
|
||||||
#include rng.js
|
#include uniqstr.js
|
||||||
#include watcher.js
|
#include watcher.js
|
||||||
|
|
||||||
const MszVideoEmbedPlayerEvents = function() {
|
const MszVideoEmbedPlayerEvents = function() {
|
||||||
|
@ -229,7 +229,7 @@ const MszVideoEmbedPlayer = function(metadata, options) {
|
||||||
videoAttrs.style.width = initialSize[0].toString() + 'px';
|
videoAttrs.style.width = initialSize[0].toString() + 'px';
|
||||||
videoAttrs.style.height = initialSize[1].toString() + 'px';
|
videoAttrs.style.height = initialSize[1].toString() + 'px';
|
||||||
|
|
||||||
const watchers = new MszWatcherCollection;
|
const watchers = new MszWatchers;
|
||||||
watchers.define(MszVideoEmbedPlayerEvents());
|
watchers.define(MszVideoEmbedPlayerEvents());
|
||||||
|
|
||||||
const player = $e({
|
const player = $e({
|
||||||
|
@ -265,12 +265,13 @@ const MszVideoEmbedPlayer = function(metadata, options) {
|
||||||
getHeight: function() { return height; },
|
getHeight: function() { return height; },
|
||||||
};
|
};
|
||||||
|
|
||||||
watchers.proxy(pub);
|
pub.watch = (name, handler) => watchers.watch(name, handler);
|
||||||
|
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
|
||||||
|
|
||||||
if(shouldObserveResize)
|
if(shouldObserveResize)
|
||||||
player.addEventListener('resize', function() { setSize(player.videoWidth, player.videoHeight); });
|
player.addEventListener('resize', function() { setSize(player.videoWidth, player.videoHeight); });
|
||||||
|
|
||||||
player.addEventListener('play', function() { watchers.call('play', pub); });
|
player.addEventListener('play', function() { watchers.call('play'); });
|
||||||
|
|
||||||
const pPlay = function() { player.play(); };
|
const pPlay = function() { player.play(); };
|
||||||
pub.play = pPlay;
|
pub.play = pPlay;
|
||||||
|
@ -280,7 +281,7 @@ const MszVideoEmbedPlayer = function(metadata, options) {
|
||||||
|
|
||||||
let stopCalled = false;
|
let stopCalled = false;
|
||||||
player.addEventListener('pause', function() {
|
player.addEventListener('pause', function() {
|
||||||
watchers.call(stopCalled ? 'stop' : 'pause', pub);
|
watchers.call(stopCalled ? 'stop' : 'pause');
|
||||||
stopCalled = false;
|
stopCalled = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -301,9 +302,9 @@ const MszVideoEmbedPlayer = function(metadata, options) {
|
||||||
player.addEventListener('volumechange', function() {
|
player.addEventListener('volumechange', function() {
|
||||||
if(lastMuteState !== player.muted) {
|
if(lastMuteState !== player.muted) {
|
||||||
lastMuteState = player.muted;
|
lastMuteState = player.muted;
|
||||||
watchers.call('mute', pub, [lastMuteState]);
|
watchers.call('mute', lastMuteState);
|
||||||
} else
|
} else
|
||||||
watchers.call('volume', pub, [player.volume]);
|
watchers.call('volume', player.volume);
|
||||||
});
|
});
|
||||||
|
|
||||||
const pSetMuted = function(state) { player.muted = state; };
|
const pSetMuted = function(state) { player.muted = state; };
|
||||||
|
@ -319,21 +320,21 @@ const MszVideoEmbedPlayer = function(metadata, options) {
|
||||||
pub.getPlaybackRate = pGetPlaybackRate;
|
pub.getPlaybackRate = pGetPlaybackRate;
|
||||||
|
|
||||||
player.addEventListener('ratechange', function() {
|
player.addEventListener('ratechange', function() {
|
||||||
watchers.call('rate', pub, [player.playbackRate]);
|
watchers.call('rate', player.playbackRate);
|
||||||
});
|
});
|
||||||
|
|
||||||
const pSetPlaybackRate = function(rate) { player.playbackRate = rate; };
|
const pSetPlaybackRate = function(rate) { player.playbackRate = rate; };
|
||||||
pub.setPlaybackRate = pSetPlaybackRate;
|
pub.setPlaybackRate = pSetPlaybackRate;
|
||||||
|
|
||||||
window.addEventListener('durationchange', function() {
|
window.addEventListener('durationchange', function() {
|
||||||
watchers.call('duration', pub, [player.duration]);
|
watchers.call('duration', player.duration);
|
||||||
});
|
});
|
||||||
|
|
||||||
const pGetDuration = function() { return player.duration; };
|
const pGetDuration = function() { return player.duration; };
|
||||||
pub.getDuration = pGetDuration;
|
pub.getDuration = pGetDuration;
|
||||||
|
|
||||||
window.addEventListener('timeupdate', function() {
|
window.addEventListener('timeupdate', function() {
|
||||||
watchers.call('time', pub, [player.currentTime]);
|
watchers.call('time', player.currentTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
const pGetTime = function() { return player.currentTime; };
|
const pGetTime = function() { return player.currentTime; };
|
||||||
|
@ -374,7 +375,7 @@ const MszVideoEmbedYouTube = function(metadata, options) {
|
||||||
currentTime = undefined,
|
currentTime = undefined,
|
||||||
isPlaying = undefined;
|
isPlaying = undefined;
|
||||||
|
|
||||||
const watchers = new MszWatcherCollection;
|
const watchers = new MszWatchers;
|
||||||
watchers.define(MszVideoEmbedPlayerEvents());
|
watchers.define(MszVideoEmbedPlayerEvents());
|
||||||
|
|
||||||
const player = $e({
|
const player = $e({
|
||||||
|
@ -410,7 +411,8 @@ const MszVideoEmbedYouTube = function(metadata, options) {
|
||||||
getPlayerId: function() { return playerId; },
|
getPlayerId: function() { return playerId; },
|
||||||
};
|
};
|
||||||
|
|
||||||
watchers.proxy(pub);
|
pub.watch = (name, handler) => watchers.watch(name, handler);
|
||||||
|
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
|
||||||
|
|
||||||
const postMessage = function(data) {
|
const postMessage = function(data) {
|
||||||
player.contentWindow.postMessage(JSON.stringify(data), ytOrigin);
|
player.contentWindow.postMessage(JSON.stringify(data), ytOrigin);
|
||||||
|
@ -463,33 +465,33 @@ const MszVideoEmbedYouTube = function(metadata, options) {
|
||||||
lastPlayerState = state;
|
lastPlayerState = state;
|
||||||
if(eventName !== undefined && eventName !== lastPlayerStateEvent) {
|
if(eventName !== undefined && eventName !== lastPlayerStateEvent) {
|
||||||
lastPlayerStateEvent = eventName;
|
lastPlayerStateEvent = eventName;
|
||||||
watchers.call(eventName, pub);
|
watchers.call(eventName);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMuted = function(muted) {
|
const handleMuted = function(muted) {
|
||||||
isMuted = muted;
|
isMuted = muted;
|
||||||
watchers.call('mute', pub, [isMuted]);
|
watchers.call('mute', isMuted);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVolume = function(value) {
|
const handleVolume = function(value) {
|
||||||
volume = value / 100;
|
volume = value / 100;
|
||||||
watchers.call('volume', pub, [volume]);
|
watchers.call('volume', volume);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRate = function(rate) {
|
const handleRate = function(rate) {
|
||||||
playbackRate = rate;
|
playbackRate = rate;
|
||||||
watchers.call('rate', pub, [playbackRate]);
|
watchers.call('rate', playbackRate);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuration = function(time) {
|
const handleDuration = function(time) {
|
||||||
duration = time;
|
duration = time;
|
||||||
watchers.call('duration', pub, [duration]);
|
watchers.call('duration', duration);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTime = function(time) {
|
const handleTime = function(time) {
|
||||||
currentTime = time;
|
currentTime = time;
|
||||||
watchers.call('time', pub, [currentTime]);
|
watchers.call('time', currentTime);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePresetRates = function(rates) {
|
const handlePresetRates = function(rates) {
|
||||||
|
@ -574,7 +576,7 @@ const MszVideoEmbedNicoNico = function(metadata, options) {
|
||||||
currentTime = undefined,
|
currentTime = undefined,
|
||||||
isPlaying = false;
|
isPlaying = false;
|
||||||
|
|
||||||
const watchers = new MszWatcherCollection;
|
const watchers = new MszWatchers;
|
||||||
watchers.define(MszVideoEmbedPlayerEvents());
|
watchers.define(MszVideoEmbedPlayerEvents());
|
||||||
|
|
||||||
const player = $e({
|
const player = $e({
|
||||||
|
@ -610,7 +612,8 @@ const MszVideoEmbedNicoNico = function(metadata, options) {
|
||||||
getPlayerId: function() { return playerId; },
|
getPlayerId: function() { return playerId; },
|
||||||
};
|
};
|
||||||
|
|
||||||
watchers.proxy(pub);
|
pub.watch = (name, handler) => watchers.watch(name, handler);
|
||||||
|
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
|
||||||
|
|
||||||
const postMessage = function(name, data) {
|
const postMessage = function(name, data) {
|
||||||
if(name === undefined)
|
if(name === undefined)
|
||||||
|
@ -660,28 +663,28 @@ const MszVideoEmbedNicoNico = function(metadata, options) {
|
||||||
|
|
||||||
if(eventName !== undefined && eventName !== lastPlayerStateEvent) {
|
if(eventName !== undefined && eventName !== lastPlayerStateEvent) {
|
||||||
lastPlayerStateEvent = eventName;
|
lastPlayerStateEvent = eventName;
|
||||||
watchers.call(eventName, pub);
|
watchers.call(eventName);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMuted = function(muted) {
|
const handleMuted = function(muted) {
|
||||||
isMuted = muted;
|
isMuted = muted;
|
||||||
watchers.call('mute', pub, [isMuted]);
|
watchers.call('mute', isMuted);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVolume = function(value) {
|
const handleVolume = function(value) {
|
||||||
volume = value;
|
volume = value;
|
||||||
watchers.call('volume', pub, [volume]);
|
watchers.call('volume', volume);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuration = function(time) {
|
const handleDuration = function(time) {
|
||||||
duration = time / 1000;
|
duration = time / 1000;
|
||||||
watchers.call('duration', pub, [duration]);
|
watchers.call('duration', duration);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTime = function(time) {
|
const handleTime = function(time) {
|
||||||
currentTime = time / 1000;
|
currentTime = time / 1000;
|
||||||
watchers.call('time', pub, [currentTime]);
|
watchers.call('time', currentTime);
|
||||||
};
|
};
|
||||||
|
|
||||||
const metadataHanders = {
|
const metadataHanders = {
|
|
@ -1,35 +1,49 @@
|
||||||
#include utils.js
|
#include utility.js
|
||||||
|
|
||||||
Misuzu.Events.Christmas2019 = function() {
|
const MszChristmas2019EventInfo = function() {
|
||||||
this.propName = propName = 'msz-christmas-' + (new Date).getFullYear().toString();
|
return {
|
||||||
|
isActive: () => {
|
||||||
|
const d = new Date;
|
||||||
|
return d.getMonth() === 11 && d.getDate() > 5 && d.getDate() < 27;
|
||||||
|
},
|
||||||
|
dispatch: () => {
|
||||||
|
const impl = new MszChristmas2019Event;
|
||||||
|
impl.dispatch();
|
||||||
|
return impl;
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
Misuzu.Events.Christmas2019.prototype.changeColour = function() {
|
|
||||||
var count = parseInt(localStorage.getItem(this.propName));
|
const MszChristmas2019Event = function() {
|
||||||
document.body.style.setProperty('--header-accent-colour', (count++ % 2) ? 'green' : 'red');
|
const propName = 'msz-christmas-' + (new Date).getFullYear().toString();
|
||||||
localStorage.setItem(this.propName, count.toString());
|
const headerBg = $q('.header__background');
|
||||||
};
|
const menuBgs = Array.from($qa('.header__desktop__submenu__background'));
|
||||||
Misuzu.Events.Christmas2019.prototype.isActive = function() {
|
|
||||||
var d = new Date;
|
if(!localStorage.getItem(propName))
|
||||||
return d.getMonth() === 11 && d.getDate() > 5 && d.getDate() < 27;
|
localStorage.setItem(propName, '0');
|
||||||
};
|
|
||||||
Misuzu.Events.Christmas2019.prototype.dispatch = function() {
|
const changeColour = () => {
|
||||||
var headerBg = $q('.header__background'),
|
let count = parseInt(localStorage.getItem(propName));
|
||||||
menuBgs = $qa('.header__desktop__submenu__background');
|
document.body.style.setProperty('--header-accent-colour', (count++ % 2) ? 'green' : 'red');
|
||||||
|
localStorage.setItem(propName, count.toString());
|
||||||
if(!localStorage.getItem(this.propName))
|
};
|
||||||
localStorage.setItem(this.propName, '0');
|
|
||||||
|
return {
|
||||||
if(headerBg)
|
changeColour: changeColour,
|
||||||
headerBg.style.transition = 'background-color .4s';
|
dispatch: () => {
|
||||||
|
if(headerBg)
|
||||||
setTimeout(function() {
|
headerBg.style.transition = 'background-color .4s';
|
||||||
if(headerBg)
|
|
||||||
headerBg.style.transition = 'background-color 1s';
|
setTimeout(() => {
|
||||||
|
if(headerBg)
|
||||||
for(var i = 0; i < menuBgs.length; i++)
|
headerBg.style.transition = 'background-color 1s';
|
||||||
menuBgs[i].style.transition = 'background-color 1s';
|
|
||||||
}, 1000);
|
for(const menuBg of menuBgs)
|
||||||
|
menuBg.style.transition = 'background-color 1s';
|
||||||
this.changeColour();
|
}, 1000);
|
||||||
setInterval(this.changeColour, 10000);
|
|
||||||
|
changeColour();
|
||||||
|
setInterval(changeColour, 10000);
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
Misuzu.Events = {};
|
const MszSeasonalEvents = function() {
|
||||||
|
const events = [];
|
||||||
|
|
||||||
#include events/christmas2019.js
|
return {
|
||||||
|
add: eventInfo => {
|
||||||
Misuzu.Events.getList = function() {
|
if(!events.includes(eventInfo))
|
||||||
return [
|
events.push(eventInfo);
|
||||||
new Misuzu.Events.Christmas2019,
|
},
|
||||||
];
|
dispatch: () => {
|
||||||
};
|
for(const info of events)
|
||||||
Misuzu.Events.dispatch = function() {
|
if(info.isActive())
|
||||||
var list = Misuzu.Events.getList();
|
info.dispatch();
|
||||||
for(var i = 0; i < list.length; ++i)
|
},
|
||||||
if(list[i].isActive())
|
};
|
||||||
list[i].dispatch();
|
|
||||||
};
|
};
|
||||||
|
|
37
assets/misuzu.js/ext/eeprom.js
Normal file
37
assets/misuzu.js/ext/eeprom.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#include utility.js
|
||||||
|
|
||||||
|
const MszEEPROM = (() => {
|
||||||
|
let eepromScript;
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(eepromScript !== undefined) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof peepPath !== 'string') {
|
||||||
|
reject();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptElem = $e({
|
||||||
|
tag: 'script',
|
||||||
|
attrs: {
|
||||||
|
src: `${peepPath}/scripts/eepromv1a.js`,
|
||||||
|
charset: 'utf-8',
|
||||||
|
type: 'text/javascript',
|
||||||
|
onerror: () => reject(),
|
||||||
|
onload: () => {
|
||||||
|
eepromScript = scriptElem;
|
||||||
|
resolve(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(scriptElem);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})();
|
|
@ -1,4 +1,4 @@
|
||||||
const Sakuya = (function() {
|
const MszSakuya = (function() {
|
||||||
const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });
|
const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });
|
||||||
const divisions = [
|
const divisions = [
|
||||||
{ amount: 60, name: 'seconds' },
|
{ amount: 60, name: 'seconds' },
|
15
assets/misuzu.js/ext/uiharu.js
Normal file
15
assets/misuzu.js/ext/uiharu.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#include utility.js
|
||||||
|
|
||||||
|
const MszUiharu = function(apiUrl) {
|
||||||
|
const maxBatchSize = 4;
|
||||||
|
const lookupOneUrl = apiUrl + '/metadata';
|
||||||
|
|
||||||
|
return {
|
||||||
|
lookupOne: async targetUrl => {
|
||||||
|
if(typeof targetUrl !== 'string')
|
||||||
|
throw 'targetUrl must be a string';
|
||||||
|
|
||||||
|
return $x.post(lookupOneUrl, { type: 'json' }, targetUrl);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,437 +0,0 @@
|
||||||
#include forum/forum.js
|
|
||||||
|
|
||||||
Misuzu.Forum.Editor = {};
|
|
||||||
Misuzu.Forum.Editor.allowWindowClose = false;
|
|
||||||
Misuzu.Forum.Editor.init = function() {
|
|
||||||
const postingForm = $q('.js-forum-posting');
|
|
||||||
if(!postingForm)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const postingButtons = postingForm.querySelector('.js-forum-posting-buttons'),
|
|
||||||
postingText = postingForm.querySelector('.js-forum-posting-text'),
|
|
||||||
postingParser = postingForm.querySelector('.js-forum-posting-parser'),
|
|
||||||
postingPreview = postingForm.querySelector('.js-forum-posting-preview'),
|
|
||||||
postingMode = postingForm.querySelector('.js-forum-posting-mode'),
|
|
||||||
previewButton = document.createElement('button'),
|
|
||||||
bbcodeButtons = $q('.forum__post__actions--bbcode'),
|
|
||||||
markdownButtons = $q('.forum__post__actions--markdown'),
|
|
||||||
markupButtons = $qa('.forum__post__action--tag');
|
|
||||||
|
|
||||||
// Initialise EEPROM, code sucks ass but it's getting nuked soon again anyway
|
|
||||||
if(typeof peepPath === 'string')
|
|
||||||
document.body.appendChild($e({
|
|
||||||
tag: 'script',
|
|
||||||
attrs: {
|
|
||||||
src: peepPath + '/eeprom.js',
|
|
||||||
charset: 'utf-8',
|
|
||||||
type: 'text/javascript',
|
|
||||||
onload: function() {
|
|
||||||
const eepromClient = new EEPROM(peepApp, peepPath + '/uploads', '');
|
|
||||||
|
|
||||||
const eepromHistory = $e({
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-history-items',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const eepromHandleFileUpload = function(file) {
|
|
||||||
const uploadElemNameValue = $e({
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-file-name-value',
|
|
||||||
title: file.name,
|
|
||||||
},
|
|
||||||
child: file.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploadElemName = $e({
|
|
||||||
tag: 'a',
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-file-name',
|
|
||||||
target: '_blank',
|
|
||||||
},
|
|
||||||
child: uploadElemNameValue,
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploadElemProgressText = $e({
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-file-progress',
|
|
||||||
},
|
|
||||||
child: 'Please wait...',
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploadElemProgressBarValue = $e({
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-file-bar-fill',
|
|
||||||
style: {
|
|
||||||
width: '0%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploadElem = $e({
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-file',
|
|
||||||
},
|
|
||||||
child: [
|
|
||||||
{
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-file-info',
|
|
||||||
},
|
|
||||||
child: [
|
|
||||||
uploadElemName,
|
|
||||||
uploadElemProgressText,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-file-bar',
|
|
||||||
},
|
|
||||||
child: uploadElemProgressBarValue,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if(eepromHistory.children.length > 0)
|
|
||||||
$ib(eepromHistory.firstChild, uploadElem);
|
|
||||||
else
|
|
||||||
eepromHistory.appendChild(uploadElem);
|
|
||||||
|
|
||||||
const explodeUploadElem = function() {
|
|
||||||
$r(uploadElem);
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadTask = eepromClient.createUpload(file);
|
|
||||||
|
|
||||||
uploadTask.onProgress = function(progressInfo) {
|
|
||||||
const progressValue = progressInfo.progress.toString() + '%';
|
|
||||||
uploadElemProgressBarValue.style.width = progressValue;
|
|
||||||
uploadElemProgressText.textContent = progressValue + ' (' + (progressInfo.total - progressInfo.loaded).toString() + ' bytes remaining)';
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadTask.onFailure = function(errorInfo) {
|
|
||||||
if(!errorInfo.userAborted) {
|
|
||||||
let errorText = 'Was unable to upload file.';
|
|
||||||
|
|
||||||
switch(errorInfo.error) {
|
|
||||||
case EEPROM.ERR_INVALID:
|
|
||||||
errorText = 'Upload request was invalid.';
|
|
||||||
break;
|
|
||||||
case EEPROM.ERR_AUTH:
|
|
||||||
errorText = 'Upload authentication failed, refresh and try again.';
|
|
||||||
break;
|
|
||||||
case EEPROM.ERR_ACCESS:
|
|
||||||
errorText = 'You\'re not allowed to upload files.';
|
|
||||||
break;
|
|
||||||
case EEPROM.ERR_GONE:
|
|
||||||
errorText = 'Upload client has a configuration error or the server is gone.';
|
|
||||||
break;
|
|
||||||
case EEPROM.ERR_DMCA:
|
|
||||||
errorText = 'This file has been uploaded before and was removed for copyright reasons, you cannot upload this file.';
|
|
||||||
break;
|
|
||||||
case EEPROM.ERR_SERVER:
|
|
||||||
errorText = 'Upload server returned a critical error, try again later.';
|
|
||||||
break;
|
|
||||||
case EEPROM.ERR_SIZE:
|
|
||||||
if(errorInfo.maxSize < 1)
|
|
||||||
errorText = 'Selected file is too large.';
|
|
||||||
else {
|
|
||||||
const _t = ['bytes', 'KB', 'MB', 'GB', 'TB'],
|
|
||||||
_i = parseInt(Math.floor(Math.log(errorInfo.maxSize) / Math.log(1024))),
|
|
||||||
_s = Math.round(errorInfo.maxSize / Math.pow(1024, _i), 2);
|
|
||||||
|
|
||||||
errorText = 'Upload may not be larger than %1 %2.'.replace('%1', _s).replace('%2', _t[_i]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadElem.classList.add('eeprom-widget-file-fail');
|
|
||||||
uploadElemProgressText.textContent = errorText;
|
|
||||||
Misuzu.showMessageBox(errorText, 'Upload Error');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadTask.onComplete = function(fileInfo) {
|
|
||||||
uploadElem.classList.add('eeprom-widget-file-done');
|
|
||||||
uploadElemName.href = fileInfo.url;
|
|
||||||
uploadElemProgressText.textContent = '';
|
|
||||||
|
|
||||||
const insertTheLinkIntoTheBoxEx2 = function() {
|
|
||||||
const parserMode = parseInt(postingParser.value);
|
|
||||||
let insertText = location.protocol + fileInfo.url;
|
|
||||||
|
|
||||||
if(parserMode == 1) { // bbcode
|
|
||||||
if(fileInfo.isImage())
|
|
||||||
insertText = '[img]' + fileInfo.url + '[/img]';
|
|
||||||
else if(fileInfo.isAudio())
|
|
||||||
insertText = '[audio]' + fileInfo.url + '[/audio]';
|
|
||||||
else if(fileInfo.isVideo())
|
|
||||||
insertText = '[video]' + fileInfo.url + '[/video]';
|
|
||||||
} else if(parserMode == 2) { // markdown
|
|
||||||
if(fileInfo.isMedia())
|
|
||||||
insertText = '![](' + fileInfo.url + ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
$insertTags(postingText, insertText, '');
|
|
||||||
postingText.value = postingText.value.trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadElemProgressText.appendChild($e({
|
|
||||||
tag: 'a',
|
|
||||||
attrs: {
|
|
||||||
href: 'javascript:void(0);',
|
|
||||||
onclick: function() { insertTheLinkIntoTheBoxEx2(); },
|
|
||||||
},
|
|
||||||
child: 'Insert',
|
|
||||||
}));
|
|
||||||
uploadElemProgressText.appendChild($t(' '));
|
|
||||||
uploadElemProgressText.appendChild($e({
|
|
||||||
tag: 'a',
|
|
||||||
attrs: {
|
|
||||||
href: 'javascript:void(0);',
|
|
||||||
onclick: function() {
|
|
||||||
eepromClient.deleteUpload(fileInfo).start();
|
|
||||||
explodeUploadElem();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
child: 'Delete',
|
|
||||||
}));
|
|
||||||
|
|
||||||
insertTheLinkIntoTheBoxEx2();
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadTask.start();
|
|
||||||
};
|
|
||||||
|
|
||||||
const eepromFormInput = $e({
|
|
||||||
tag: 'input',
|
|
||||||
attrs: {
|
|
||||||
type: 'file',
|
|
||||||
multiple: 'multiple',
|
|
||||||
className: 'eeprom-widget-form-input',
|
|
||||||
onchange: function(ev) {
|
|
||||||
const files = this.files;
|
|
||||||
for(const file of files)
|
|
||||||
eepromHandleFileUpload(file);
|
|
||||||
this.value = '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const eepromForm = $e({
|
|
||||||
tag: 'label',
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-form',
|
|
||||||
},
|
|
||||||
child: [
|
|
||||||
eepromFormInput,
|
|
||||||
{
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-form-text',
|
|
||||||
},
|
|
||||||
child: 'Select Files...',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const eepromWidget = $e({
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget',
|
|
||||||
},
|
|
||||||
child: [
|
|
||||||
eepromForm,
|
|
||||||
{
|
|
||||||
attrs: {
|
|
||||||
className: 'eeprom-widget-history',
|
|
||||||
},
|
|
||||||
child: eepromHistory,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
postingForm.appendChild(eepromWidget);
|
|
||||||
|
|
||||||
postingText.addEventListener('paste', function(ev) {
|
|
||||||
if(ev.clipboardData && ev.clipboardData.files.length > 0) {
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
const files = ev.clipboardData.files;
|
|
||||||
for(const file of files)
|
|
||||||
eepromHandleFileUpload(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.addEventListener('dragenter', function(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
});
|
|
||||||
document.body.addEventListener('dragover', function(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
});
|
|
||||||
document.body.addEventListener('dragleave', function(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
});
|
|
||||||
document.body.addEventListener('drop', function(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
if(ev.dataTransfer && ev.dataTransfer.files.length > 0) {
|
|
||||||
const files = ev.dataTransfer.files;
|
|
||||||
for(const file of files)
|
|
||||||
eepromHandleFileUpload(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onerror: function(ev) {
|
|
||||||
console.error('Failed to initialise EEPROM: ', ev);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// hack: don't prompt user when hitting submit, really need to make this not stupid.
|
|
||||||
postingButtons.firstElementChild.addEventListener('click', function() {
|
|
||||||
Misuzu.Forum.Editor.allowWindowClose = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('beforeunload', function(ev) {
|
|
||||||
if(!Misuzu.Forum.Editor.allowWindowClose && postingText.value.length > 0) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.returnValue = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for(var i = 0; i < markupButtons.length; ++i)
|
|
||||||
(function(currentBtn) {
|
|
||||||
currentBtn.addEventListener('click', function(ev) {
|
|
||||||
$insertTags(postingText, currentBtn.dataset.tagOpen, currentBtn.dataset.tagClose);
|
|
||||||
});
|
|
||||||
})(markupButtons[i]);
|
|
||||||
|
|
||||||
Misuzu.Forum.Editor.switchButtons(parseInt(postingParser.value));
|
|
||||||
|
|
||||||
var lastPostText = '',
|
|
||||||
lastPostParser = null;
|
|
||||||
|
|
||||||
postingParser.addEventListener('change', function() {
|
|
||||||
var postParser = parseInt(postingParser.value);
|
|
||||||
Misuzu.Forum.Editor.switchButtons(postParser);
|
|
||||||
|
|
||||||
if(postingPreview.hasAttribute('hidden'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// dunno if this would even be possible, but ech
|
|
||||||
if(postParser === lastPostParser)
|
|
||||||
return;
|
|
||||||
|
|
||||||
postingParser.setAttribute('disabled', 'disabled');
|
|
||||||
previewButton.setAttribute('disabled', 'disabled');
|
|
||||||
previewButton.classList.add('input__button--busy');
|
|
||||||
|
|
||||||
Misuzu.Forum.Editor.renderPreview(postParser, lastPostText, function(success, text) {
|
|
||||||
if(!success) {
|
|
||||||
Misuzu.showMessageBox(text);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
postingPreview.classList[postParser == 2 ? 'add' : 'remove']('markdown');
|
|
||||||
|
|
||||||
lastPostParser = postParser;
|
|
||||||
postingPreview.innerHTML = text;
|
|
||||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
|
||||||
|
|
||||||
previewButton.removeAttribute('disabled');
|
|
||||||
postingParser.removeAttribute('disabled');
|
|
||||||
previewButton.classList.remove('input__button--busy');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
previewButton.className = 'input__button';
|
|
||||||
previewButton.textContent = 'Preview';
|
|
||||||
previewButton.type = 'button';
|
|
||||||
previewButton.value = 'preview';
|
|
||||||
previewButton.addEventListener('click', function() {
|
|
||||||
if(previewButton.value === 'back') {
|
|
||||||
postingPreview.setAttribute('hidden', 'hidden');
|
|
||||||
postingText.removeAttribute('hidden');
|
|
||||||
previewButton.value = 'preview';
|
|
||||||
previewButton.textContent = 'Preview';
|
|
||||||
postingMode.textContent = postingMode.dataset.original;
|
|
||||||
postingMode.dataset.original = null;
|
|
||||||
} else {
|
|
||||||
var postText = postingText.value,
|
|
||||||
postParser = parseInt(postingParser.value);
|
|
||||||
|
|
||||||
if(lastPostText === postText && lastPostParser === postParser) {
|
|
||||||
postingPreview.removeAttribute('hidden');
|
|
||||||
postingText.setAttribute('hidden', 'hidden');
|
|
||||||
previewButton.value = 'back';
|
|
||||||
previewButton.textContent = 'Edit';
|
|
||||||
postingMode.dataset.original = postingMode.textContent;
|
|
||||||
postingMode.textContent = 'Previewing';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
postingParser.setAttribute('disabled', 'disabled');
|
|
||||||
previewButton.setAttribute('disabled', 'disabled');
|
|
||||||
previewButton.classList.add('input__button--busy');
|
|
||||||
|
|
||||||
Misuzu.Forum.Editor.renderPreview(postParser, postText, function(success, text) {
|
|
||||||
if(!success) {
|
|
||||||
Misuzu.showMessageBox(text);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
postingPreview.classList[postParser == 2 ? 'add' : 'remove']('markdown');
|
|
||||||
|
|
||||||
lastPostText = postText;
|
|
||||||
lastPostParser = postParser;
|
|
||||||
postingPreview.innerHTML = text;
|
|
||||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
|
||||||
|
|
||||||
postingPreview.removeAttribute('hidden');
|
|
||||||
postingText.setAttribute('hidden', 'hidden');
|
|
||||||
previewButton.value = 'back';
|
|
||||||
previewButton.textContent = 'Back';
|
|
||||||
previewButton.removeAttribute('disabled');
|
|
||||||
postingParser.removeAttribute('disabled');
|
|
||||||
previewButton.classList.remove('input__button--busy');
|
|
||||||
postingMode.dataset.original = postingMode.textContent;
|
|
||||||
postingMode.textContent = 'Previewing';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
postingButtons.insertBefore(previewButton, postingButtons.firstChild);
|
|
||||||
};
|
|
||||||
Misuzu.Forum.Editor.switchButtons = function(parser) {
|
|
||||||
var bbcodeButtons = $q('.forum__post__actions--bbcode'),
|
|
||||||
markdownButtons = $q('.forum__post__actions--markdown');
|
|
||||||
|
|
||||||
bbcodeButtons.hidden = parser != 1;
|
|
||||||
markdownButtons.hidden = parser != 2;
|
|
||||||
};
|
|
||||||
Misuzu.Forum.Editor.renderPreview = function(parser, text, callback) {
|
|
||||||
if(!callback)
|
|
||||||
return;
|
|
||||||
parser = parseInt(parser);
|
|
||||||
text = text || '';
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest,
|
|
||||||
formData = new FormData;
|
|
||||||
|
|
||||||
formData.append('post[mode]', 'preview');
|
|
||||||
formData.append('post[text]', text);
|
|
||||||
formData.append('post[parser]', parser.toString());
|
|
||||||
|
|
||||||
xhr.addEventListener('readystatechange', function() {
|
|
||||||
if(xhr.readyState !== XMLHttpRequest.DONE)
|
|
||||||
return;
|
|
||||||
if(xhr.status === 200)
|
|
||||||
callback(true, xhr.response);
|
|
||||||
else
|
|
||||||
callback(false, 'Failed to render preview.');
|
|
||||||
});
|
|
||||||
// need to figure out a url registry system again, current one is too much overhead so lets just do this for now
|
|
||||||
xhr.open('POST', '/forum/posting.php');
|
|
||||||
xhr.withCredentials = true;
|
|
||||||
xhr.send(formData);
|
|
||||||
};
|
|
291
assets/misuzu.js/forum/editor.jsx
Normal file
291
assets/misuzu.js/forum/editor.jsx
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
#include msgbox.jsx
|
||||||
|
#include parsing.js
|
||||||
|
#include utility.js
|
||||||
|
#include ext/eeprom.js
|
||||||
|
|
||||||
|
let MszForumEditorAllowClose = false;
|
||||||
|
|
||||||
|
const MszForumEditor = function(form) {
|
||||||
|
if(!(form instanceof Element))
|
||||||
|
throw 'form must be an instance of element';
|
||||||
|
|
||||||
|
const buttonsElem = form.querySelector('.js-forum-posting-buttons'),
|
||||||
|
textElem = form.querySelector('.js-forum-posting-text'),
|
||||||
|
parserElem = form.querySelector('.js-forum-posting-parser'),
|
||||||
|
previewElem = form.querySelector('.js-forum-posting-preview'),
|
||||||
|
modeElem = form.querySelector('.js-forum-posting-mode'),
|
||||||
|
markupActs = form.querySelector('.js-forum-posting-actions');
|
||||||
|
|
||||||
|
let lastPostText = '',
|
||||||
|
lastPostParser;
|
||||||
|
|
||||||
|
MszEEPROM.init()
|
||||||
|
.catch(() => console.error('Failed to initialise EEPROM'))
|
||||||
|
.then(() => {
|
||||||
|
const eepromClient = new EEPROM(peepApp, peepPath);
|
||||||
|
const eepromHistory = <div class="eeprom-widget-history-items"/>;
|
||||||
|
|
||||||
|
const eepromHandleFileUpload = async file => {
|
||||||
|
const uploadElemNameValue = <div class="eeprom-widget-file-name-value" title={file.name}>{file.name}</div>;
|
||||||
|
const uploadElemName = <a class="eeprom-widget-file-name" target="_blank">{uploadElemNameValue}</a>;
|
||||||
|
const uploadElemProgressText = <div class="eeprom-widget-file-progress">Please wait...</div>;
|
||||||
|
const uploadElemProgressBarValue = <div class="eeprom-widget-file-bar-fill" style={{ width: '0%' }}/>;
|
||||||
|
const uploadElem = <div class="eeprom-widget-file">
|
||||||
|
<div class="eeprom-widget-file-info">
|
||||||
|
{uploadElemName}
|
||||||
|
{uploadElemProgressText}
|
||||||
|
</div>
|
||||||
|
<div class="eeprom-widget-file-bar">
|
||||||
|
{uploadElemProgressBarValue}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
if(eepromHistory.children.length > 0)
|
||||||
|
$ib(eepromHistory.firstChild, uploadElem);
|
||||||
|
else
|
||||||
|
eepromHistory.appendChild(uploadElem);
|
||||||
|
|
||||||
|
const explodeUploadElem = () => $r(uploadElem);
|
||||||
|
const uploadTask = eepromClient.create(file);
|
||||||
|
|
||||||
|
uploadTask.onProgress(prog => {
|
||||||
|
uploadElemProgressBarValue.style.width = `${Math.ceil(prog.progress * 100)}%`;
|
||||||
|
uploadElemProgressText.textContent = `${prog.progress.toLocaleString(undefined, { style: 'percent' })} (${prog.total - prog.loaded} bytes remaining)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileInfo = await uploadTask.start();
|
||||||
|
|
||||||
|
uploadElem.classList.add('eeprom-widget-file-done');
|
||||||
|
uploadElemName.href = fileInfo.url;
|
||||||
|
uploadElemProgressText.textContent = '';
|
||||||
|
|
||||||
|
const insertTheLinkIntoTheBoxEx2 = () => {
|
||||||
|
const parserMode = parseInt(parserElem.value);
|
||||||
|
let insertText = location.protocol + fileInfo.url;
|
||||||
|
|
||||||
|
if(parserMode == 1) { // bbcode
|
||||||
|
if(fileInfo.isImage())
|
||||||
|
insertText = `[img]${fileInfo.url}[/img]`;
|
||||||
|
else if(fileInfo.isAudio())
|
||||||
|
insertText = `[audio]${fileInfo.url}[/audio]`;
|
||||||
|
else if(fileInfo.isVideo())
|
||||||
|
insertText = `[video]${fileInfo.url}[/video]`;
|
||||||
|
} else if(parserMode == 2) { // markdown
|
||||||
|
if(fileInfo.isMedia())
|
||||||
|
insertText = `![](${fileInfo.url})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertTags(textElem, insertText, '');
|
||||||
|
textElem.value = textElem.value.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
uploadElemProgressText.appendChild(<a href="javascript:void(0)" onclick={() => insertTheLinkIntoTheBoxEx2()}>Insert</a>);
|
||||||
|
uploadElemProgressText.appendChild($t(' '));
|
||||||
|
uploadElemProgressText.appendChild(<a href="javascript:void(0)" onclick={() => {
|
||||||
|
eepromClient.delete(fileInfo)
|
||||||
|
.then(() => explodeUploadElem())
|
||||||
|
.catch(ex => {
|
||||||
|
console.error(ex);
|
||||||
|
MszShowMessageBox(ex, 'Upload Error');
|
||||||
|
});
|
||||||
|
}}>Delete</a>);
|
||||||
|
|
||||||
|
insertTheLinkIntoTheBoxEx2();
|
||||||
|
} catch(ex) {
|
||||||
|
let errorText = 'Upload aborted.';
|
||||||
|
|
||||||
|
if(!ex.aborted) {
|
||||||
|
console.error(ex);
|
||||||
|
errorText = ex.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadElem.classList.add('eeprom-widget-file-fail');
|
||||||
|
uploadElemProgressText.textContent = errorText;
|
||||||
|
await MszShowMessageBox(errorText, 'Upload Error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const eepromFormInput = <input type="file" multiple={true} class="eeprom-widget-form-input"
|
||||||
|
onchange={() => {
|
||||||
|
const files = eepromFormInput.files;
|
||||||
|
for(const file of files)
|
||||||
|
eepromHandleFileUpload(file);
|
||||||
|
eepromFormInput.value = '';
|
||||||
|
}}/>;
|
||||||
|
|
||||||
|
const eepromForm = <label class="eeprom-widget-form">
|
||||||
|
{eepromFormInput}
|
||||||
|
<div class="eeprom-widget-form-text">
|
||||||
|
Select Files...
|
||||||
|
</div>
|
||||||
|
</label>;
|
||||||
|
|
||||||
|
const eepromWidget = <div class="eeprom-widget">
|
||||||
|
{eepromForm}
|
||||||
|
<div class="eeprom-widget-history">
|
||||||
|
{eepromHistory}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
form.appendChild(eepromWidget);
|
||||||
|
|
||||||
|
textElem.addEventListener('paste', ev => {
|
||||||
|
if(ev.clipboardData && ev.clipboardData.files.length > 0) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
const files = ev.clipboardData.files;
|
||||||
|
for(const file of files)
|
||||||
|
eepromHandleFileUpload(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.addEventListener('dragenter', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
});
|
||||||
|
document.body.addEventListener('dragover', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
});
|
||||||
|
document.body.addEventListener('dragleave', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
});
|
||||||
|
document.body.addEventListener('drop', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
if(ev.dataTransfer && ev.dataTransfer.files.length > 0) {
|
||||||
|
const files = ev.dataTransfer.files;
|
||||||
|
for(const file of files)
|
||||||
|
eepromHandleFileUpload(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// hack: don't prompt user when hitting submit, really need to make this not stupid.
|
||||||
|
buttonsElem.firstChild.addEventListener('click', () => MszForumEditorAllowClose = true);
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function(ev) {
|
||||||
|
if(!MszForumEditorAllowClose && textElem.value.length > 0) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.returnValue = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const switchButtons = parser => {
|
||||||
|
$rc(markupActs);
|
||||||
|
|
||||||
|
const tags = MszParsing.getTagsFor(parser);
|
||||||
|
for(const tag of tags)
|
||||||
|
markupActs.appendChild(<button class={['forum__post__action', 'forum__post__action--tag', `forum__post__action--${tag.name}`]}
|
||||||
|
type="button" title={tag.summary} onclick={() => $insertTags(textElem, tag.open, tag.close)}>
|
||||||
|
<i class={tag.icon}/>
|
||||||
|
</button>);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPreview = async (parser, text) => {
|
||||||
|
if(typeof text !== 'string')
|
||||||
|
return '';
|
||||||
|
|
||||||
|
const formData = new FormData;
|
||||||
|
formData.append('post[mode]', 'preview');
|
||||||
|
formData.append('post[text]', text);
|
||||||
|
formData.append('post[parser]', parseInt(parser));
|
||||||
|
|
||||||
|
const result = await $x.post('/forum/posting.php', { authed: true }, formData);
|
||||||
|
|
||||||
|
return result.body();
|
||||||
|
};
|
||||||
|
|
||||||
|
const previewBtn = <button class="input__button" type="button" value="preview">Preview</button>;
|
||||||
|
previewBtn.addEventListener('click', function() {
|
||||||
|
if(previewBtn.value === 'back') {
|
||||||
|
previewElem.setAttribute('hidden', 'hidden');
|
||||||
|
textElem.removeAttribute('hidden');
|
||||||
|
previewBtn.value = 'preview';
|
||||||
|
previewBtn.textContent = 'Preview';
|
||||||
|
modeElem.textContent = modeElem.dataset.original;
|
||||||
|
modeElem.dataset.original = null;
|
||||||
|
} else {
|
||||||
|
const postText = textElem.value,
|
||||||
|
postParser = parseInt(parserElem.value);
|
||||||
|
|
||||||
|
if(lastPostText === postText && lastPostParser === postParser) {
|
||||||
|
previewElem.removeAttribute('hidden');
|
||||||
|
textElem.setAttribute('hidden', 'hidden');
|
||||||
|
previewBtn.value = 'back';
|
||||||
|
previewBtn.textContent = 'Edit';
|
||||||
|
modeElem.dataset.original = modeElem.textContent;
|
||||||
|
modeElem.textContent = 'Previewing';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parserElem.setAttribute('disabled', 'disabled');
|
||||||
|
previewBtn.setAttribute('disabled', 'disabled');
|
||||||
|
previewBtn.classList.add('input__button--busy');
|
||||||
|
|
||||||
|
renderPreview(postParser, postText)
|
||||||
|
.catch(() => {
|
||||||
|
previewElem.innerHTML = '';
|
||||||
|
MszShowMessageBox('Failed to render preview.');
|
||||||
|
})
|
||||||
|
.then(body => {
|
||||||
|
previewElem.classList.toggle('markdown', postParser === 2);
|
||||||
|
|
||||||
|
lastPostText = postText;
|
||||||
|
lastPostParser = postParser;
|
||||||
|
previewElem.innerHTML = body;
|
||||||
|
|
||||||
|
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||||
|
|
||||||
|
previewElem.removeAttribute('hidden');
|
||||||
|
textElem.setAttribute('hidden', 'hidden');
|
||||||
|
previewBtn.value = 'back';
|
||||||
|
previewBtn.textContent = 'Back';
|
||||||
|
previewBtn.removeAttribute('disabled');
|
||||||
|
parserElem.removeAttribute('disabled');
|
||||||
|
previewBtn.classList.remove('input__button--busy');
|
||||||
|
modeElem.dataset.original = modeElem.textContent;
|
||||||
|
modeElem.textContent = 'Previewing';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
buttonsElem.insertBefore(previewBtn, buttonsElem.firstChild);
|
||||||
|
|
||||||
|
switchButtons(parserElem.value);
|
||||||
|
|
||||||
|
parserElem.addEventListener('change', () => {
|
||||||
|
const postParser = parseInt(parserElem.value);
|
||||||
|
switchButtons(postParser);
|
||||||
|
|
||||||
|
if(previewElem.hasAttribute('hidden'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// dunno if this would even be possible, but ech
|
||||||
|
if(postParser === lastPostParser)
|
||||||
|
return;
|
||||||
|
|
||||||
|
parserElem.setAttribute('disabled', 'disabled');
|
||||||
|
previewBtn.setAttribute('disabled', 'disabled');
|
||||||
|
previewBtn.classList.add('input__button--busy');
|
||||||
|
|
||||||
|
renderPreview(postParser, lastPostText)
|
||||||
|
.catch(() => {
|
||||||
|
previewElem.innerHTML = '';
|
||||||
|
MszShowMessageBox('Failed to render preview.');
|
||||||
|
})
|
||||||
|
.then(body => {
|
||||||
|
previewElem.classList.add('markdown', postParser === 2);
|
||||||
|
lastPostParser = postParser;
|
||||||
|
previewElem.innerHTML = body;
|
||||||
|
|
||||||
|
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||||
|
|
||||||
|
previewBtn.removeAttribute('disabled');
|
||||||
|
parserElem.removeAttribute('disabled');
|
||||||
|
previewBtn.classList.remove('input__button--busy');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -1 +0,0 @@
|
||||||
Misuzu.Forum = {};
|
|
|
@ -1,135 +1,90 @@
|
||||||
#include sakuya.js
|
#include utility.js
|
||||||
|
#include embed/embed.js
|
||||||
var Misuzu = function() {
|
#include events/christmas2019.js
|
||||||
Sakuya.trackElements($qa('time'));
|
|
||||||
hljs.initHighlighting();
|
|
||||||
|
|
||||||
MszEmbed.init(location.protocol + '//uiharu.' + location.host);
|
|
||||||
|
|
||||||
Misuzu.initQuickSubmit(); // only used by the forum posting form
|
|
||||||
Misuzu.Forum.Editor.init();
|
|
||||||
Misuzu.Events.dispatch();
|
|
||||||
Misuzu.initLoginPage();
|
|
||||||
|
|
||||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
|
||||||
};
|
|
||||||
|
|
||||||
#include utils.js
|
|
||||||
#include embed.js
|
|
||||||
#include forum/editor.js
|
|
||||||
#include events/events.js
|
#include events/events.js
|
||||||
|
#include ext/sakuya.js
|
||||||
|
#include forum/editor.jsx
|
||||||
|
#include messages/messages.js
|
||||||
|
|
||||||
Misuzu.showMessageBox = function(text, title, buttons) {
|
(async () => {
|
||||||
if($q('.messagebox'))
|
const initLoginPage = async () => {
|
||||||
return false;
|
const forms = Array.from($qa('.js-login-form'));
|
||||||
|
if(forms.length < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
text = text || '';
|
const updateForm = async (avatar, userName) => {
|
||||||
title = title || '';
|
if(!(avatar instanceof Element) || !(userName instanceof Element))
|
||||||
buttons = buttons || [];
|
|
||||||
|
|
||||||
var element = document.createElement('div');
|
|
||||||
element.className = 'messagebox';
|
|
||||||
|
|
||||||
var container = element.appendChild(document.createElement('div'));
|
|
||||||
container.className = 'container messagebox__container';
|
|
||||||
|
|
||||||
var titleElement = container.appendChild(document.createElement('div')),
|
|
||||||
titleBackground = titleElement.appendChild(document.createElement('div')),
|
|
||||||
titleText = titleElement.appendChild(document.createElement('div'));
|
|
||||||
|
|
||||||
titleElement.className = 'container__title';
|
|
||||||
titleBackground.className = 'container__title__background';
|
|
||||||
titleText.className = 'container__title__text';
|
|
||||||
titleText.textContent = title || 'Information';
|
|
||||||
|
|
||||||
var textElement = container.appendChild(document.createElement('div'));
|
|
||||||
textElement.className = 'container__content';
|
|
||||||
textElement.textContent = text;
|
|
||||||
|
|
||||||
var buttonsContainer = container.appendChild(document.createElement('div'));
|
|
||||||
buttonsContainer.className = 'messagebox__buttons';
|
|
||||||
|
|
||||||
var firstButton = null;
|
|
||||||
|
|
||||||
if(buttons.length < 1) {
|
|
||||||
firstButton = buttonsContainer.appendChild(document.createElement('button'));
|
|
||||||
firstButton.className = 'input__button';
|
|
||||||
firstButton.textContent = 'OK';
|
|
||||||
firstButton.addEventListener('click', function() { element.remove(); });
|
|
||||||
} else {
|
|
||||||
for(var i = 0; i < buttons.length; i++) {
|
|
||||||
var button = buttonsContainer.appendChild(document.createElement('button'));
|
|
||||||
button.className = 'input__button';
|
|
||||||
button.textContent = buttons[i].text;
|
|
||||||
button.addEventListener('click', function() {
|
|
||||||
element.remove();
|
|
||||||
buttons[i].callback();
|
|
||||||
});
|
|
||||||
|
|
||||||
if(firstButton === null)
|
|
||||||
firstButton = button;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.appendChild(element);
|
|
||||||
firstButton.focus();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
Misuzu.initLoginPage = function() {
|
|
||||||
var updateForm = function(avatarElem, usernameElem) {
|
|
||||||
var xhr = new XMLHttpRequest;
|
|
||||||
xhr.addEventListener('readystatechange', function() {
|
|
||||||
if(xhr.readyState !== 4)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var json = JSON.parse(xhr.responseText);
|
const result = (await $x.get(`/auth/login.php?resolve=1&name=${encodeURIComponent(userName.value)}`, { type: 'json' })).body();
|
||||||
if(!json)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(json.name)
|
avatar.src = result.avatar;
|
||||||
usernameElem.value = json.name;
|
if(result.name.length > 0)
|
||||||
avatarElem.src = json.avatar;
|
userName.value = result.name;
|
||||||
});
|
};
|
||||||
// need to figure out a url registry system again, current one is too much overhead so lets just do this for now
|
|
||||||
xhr.open('GET', '/auth/login.php?resolve=1&name=' + encodeURIComponent(usernameElem.value));
|
|
||||||
xhr.send();
|
|
||||||
};
|
|
||||||
|
|
||||||
var loginForms = $c('js-login-form');
|
for(const form of forms) {
|
||||||
|
const avatar = form.querySelector('.js-login-avatar');
|
||||||
|
const userName = form.querySelector('.js-login-username');
|
||||||
|
let timeOut;
|
||||||
|
|
||||||
for(var i = 0; i < loginForms.length; ++i)
|
await updateForm(avatar, userName);
|
||||||
(function(form) {
|
|
||||||
var loginTimeOut = 0,
|
|
||||||
loginAvatar = form.querySelector('.js-login-avatar'),
|
|
||||||
loginUsername = form.querySelector('.js-login-username');
|
|
||||||
|
|
||||||
updateForm(loginAvatar, loginUsername);
|
userName.addEventListener('input', function() {
|
||||||
loginUsername.addEventListener('keyup', function() {
|
if(timeOut !== undefined)
|
||||||
if(loginTimeOut)
|
|
||||||
return;
|
return;
|
||||||
loginTimeOut = setTimeout(function() {
|
|
||||||
updateForm(loginAvatar, loginUsername);
|
timeOut = setTimeout(() => {
|
||||||
clearTimeout(loginTimeOut);
|
updateForm(avatar, userName)
|
||||||
loginTimeOut = 0;
|
.finally(() => {
|
||||||
|
clearTimeout(timeOut);
|
||||||
|
timeOut = undefined;
|
||||||
|
});
|
||||||
}, 750);
|
}, 750);
|
||||||
});
|
});
|
||||||
})(loginForms[i]);
|
}
|
||||||
};
|
};
|
||||||
Misuzu.initQuickSubmit = function() {
|
|
||||||
var ctrlSubmit = Array.from($qa('.js-quick-submit, .js-ctrl-enter-submit'));
|
|
||||||
if(!ctrlSubmit)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for(var i = 0; i < ctrlSubmit.length; ++i)
|
const initQuickSubmit = () => {
|
||||||
ctrlSubmit[i].addEventListener('keydown', function(ev) {
|
const elems = Array.from($qa('.js-quick-submit, .js-ctrl-enter-submit'));
|
||||||
if((ev.code === 'Enter' || ev.code === 'NumpadEnter') // i hate this fucking language so much
|
if(elems.length < 1)
|
||||||
&& ev.ctrlKey && !ev.altKey && !ev.shiftKey && !ev.metaKey) {
|
return;
|
||||||
// hack: prevent forum editor from screaming when using this keycombo
|
|
||||||
// can probably be done in a less stupid manner
|
|
||||||
Misuzu.Forum.Editor.allowWindowClose = true;
|
|
||||||
|
|
||||||
this.form.submit();
|
for(const elem of elems)
|
||||||
ev.preventDefault();
|
elem.addEventListener('keydown', ev => {
|
||||||
}
|
if((ev.code === 'Enter' || ev.code === 'NumpadEnter') && ev.ctrlKey && !ev.altKey && !ev.shiftKey && !ev.metaKey) {
|
||||||
});
|
// hack: prevent forum editor from screaming when using this keycombo
|
||||||
};
|
// can probably be done in a less stupid manner
|
||||||
|
MszForumEditorAllowClose = true;
|
||||||
|
|
||||||
|
elem.form.submit();
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
MszSakuya.trackElements($qa('time'));
|
||||||
|
hljs.highlightAll();
|
||||||
|
|
||||||
|
MszEmbed.init(`${location.protocol}//uiharu.${location.host}`);
|
||||||
|
|
||||||
|
// only used by the forum posting form
|
||||||
|
initQuickSubmit();
|
||||||
|
const forumPostingForm = $q('.js-forum-posting');
|
||||||
|
if(forumPostingForm !== null)
|
||||||
|
MszForumEditor(forumPostingForm);
|
||||||
|
|
||||||
|
const events = new MszSeasonalEvents;
|
||||||
|
events.add(new MszChristmas2019EventInfo);
|
||||||
|
events.dispatch();
|
||||||
|
|
||||||
|
await initLoginPage();
|
||||||
|
|
||||||
|
MszMessages();
|
||||||
|
|
||||||
|
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||||
|
} catch(ex) {
|
||||||
|
console.error(ex);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
89
assets/misuzu.js/messages/actbtn.js
Normal file
89
assets/misuzu.js/messages/actbtn.js
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
#include watcher.js
|
||||||
|
|
||||||
|
const MszMessagesActionButton = function(button, stateless) {
|
||||||
|
if(!(button instanceof Element))
|
||||||
|
throw 'button must be an element';
|
||||||
|
|
||||||
|
const stateful = !stateless;
|
||||||
|
const pub = {};
|
||||||
|
|
||||||
|
const icon = button.querySelector('.js-messages-button-icon i');
|
||||||
|
const label = button.querySelector('.js-messages-button-label');
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
if(stateful) {
|
||||||
|
icon.className = button.dataset[`${button.dataset.state}Ico`];
|
||||||
|
label.textContent = button.dataset[`${button.dataset.state}Str`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pub.update = update;
|
||||||
|
|
||||||
|
const stateWatcher = new MszWatcher;
|
||||||
|
const getState = () => button.dataset.state !== 'inactive';
|
||||||
|
const setState = state => {
|
||||||
|
button.dataset.state = state ? 'active' : 'inactive';
|
||||||
|
update();
|
||||||
|
stateWatcher.call(getState());
|
||||||
|
};
|
||||||
|
|
||||||
|
if(stateful) {
|
||||||
|
pub.getState = getState;
|
||||||
|
pub.setState = setState;
|
||||||
|
pub.watchState = handler => { stateWatcher.watch(handler, getState()); };
|
||||||
|
pub.unwatchState = handler => { stateWatcher.unwatch(handler); };
|
||||||
|
}
|
||||||
|
|
||||||
|
let clickAction;
|
||||||
|
const click = async () => {
|
||||||
|
if(clickAction !== undefined) {
|
||||||
|
if(stateful) {
|
||||||
|
const result = await clickAction(getState());
|
||||||
|
if(typeof result === 'boolean')
|
||||||
|
setState(result);
|
||||||
|
} else
|
||||||
|
await clickAction();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pub.click = click;
|
||||||
|
|
||||||
|
button.addEventListener('click', () => click());
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
pub.setAction = action => {
|
||||||
|
if(typeof action !== 'function')
|
||||||
|
throw 'action must be a function';
|
||||||
|
clickAction = action;
|
||||||
|
};
|
||||||
|
|
||||||
|
let preventEnable = false;
|
||||||
|
|
||||||
|
pub.getEnabled = () => !button.disabled;
|
||||||
|
pub.setEnabled = state => {
|
||||||
|
if(!preventEnable)
|
||||||
|
button.disabled = !state;
|
||||||
|
};
|
||||||
|
pub.disableWith = async callback => {
|
||||||
|
if(typeof callback !== 'function')
|
||||||
|
throw 'callback must be a function';
|
||||||
|
if(preventEnable)
|
||||||
|
throw 'preventEnable is true';
|
||||||
|
|
||||||
|
preventEnable = true;
|
||||||
|
const wasDisabled = button.disabled;
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await callback();
|
||||||
|
} finally {
|
||||||
|
button.disabled = wasDisabled;
|
||||||
|
preventEnable = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub.setHidden = state => {
|
||||||
|
button.hidden = state;
|
||||||
|
};
|
||||||
|
|
||||||
|
return pub;
|
||||||
|
};
|
167
assets/misuzu.js/messages/list.js
Normal file
167
assets/misuzu.js/messages/list.js
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
#include utility.js
|
||||||
|
#include watcher.js
|
||||||
|
|
||||||
|
const MsgMessagesList = function(list) {
|
||||||
|
if(!(list instanceof Element))
|
||||||
|
throw 'list must be an element';
|
||||||
|
|
||||||
|
const watchers = new MszWatchers;
|
||||||
|
watchers.define(['select']);
|
||||||
|
|
||||||
|
let selectedCount = 0;
|
||||||
|
|
||||||
|
const items = Array.from(list.querySelectorAll('.js-messages-entry')).map(elem => {
|
||||||
|
const item = new MsgMessagesEntry(elem);
|
||||||
|
item.onSelectedChange((state, initial) => {
|
||||||
|
if(state)
|
||||||
|
++selectedCount;
|
||||||
|
else if(!initial)
|
||||||
|
--selectedCount;
|
||||||
|
|
||||||
|
if(!initial)
|
||||||
|
watchers.call('select', selectedCount, items.length);
|
||||||
|
});
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
const recountSelected = () => {
|
||||||
|
selectedCount = 0;
|
||||||
|
|
||||||
|
for(const item of items)
|
||||||
|
if(item.getSelected())
|
||||||
|
++selectedCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectedChange = handler => {
|
||||||
|
watchers.watch('select', handler, selectedCount, items.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectedChange(selectedCount => {
|
||||||
|
const state = selectedCount > 0;
|
||||||
|
for(const item of items)
|
||||||
|
item.setClickIsSelect(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
getItems: () => items,
|
||||||
|
getItemsCount: () => items.length,
|
||||||
|
getSelectedItems: () => {
|
||||||
|
const selected = [];
|
||||||
|
|
||||||
|
for(const item of items)
|
||||||
|
if(item.getSelected())
|
||||||
|
selected.push(item);
|
||||||
|
|
||||||
|
return selected;
|
||||||
|
},
|
||||||
|
removeItem: item => {
|
||||||
|
$ari(items, item);
|
||||||
|
$r(item.getElement());
|
||||||
|
recountSelected();
|
||||||
|
watchers.call('select', selectedCount, items.length);
|
||||||
|
},
|
||||||
|
getAllSelected: () => {
|
||||||
|
if(items.length < 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for(const item of items)
|
||||||
|
if(!item.getSelected())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
setAllSelected: state => {
|
||||||
|
for(const item of items)
|
||||||
|
item.setSelected(state);
|
||||||
|
selectedCount = state ? items.length : 0;
|
||||||
|
watchers.call('select', selectedCount, items.length);
|
||||||
|
},
|
||||||
|
onSelectedChange: onSelectedChange,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const MsgMessagesEntry = function(entry) {
|
||||||
|
if(!(entry instanceof Element))
|
||||||
|
throw 'entry must be an element';
|
||||||
|
|
||||||
|
const msgId = entry.dataset.msgId;
|
||||||
|
|
||||||
|
const unreadElem = entry.querySelector('.js-messages-entry-unread');
|
||||||
|
const isRead = () => entry.dataset.msgRead === 'read';
|
||||||
|
const setRead = state => {
|
||||||
|
if(state) {
|
||||||
|
entry.dataset.msgRead = 'read';
|
||||||
|
unreadElem.hidden = true;
|
||||||
|
} else {
|
||||||
|
entry.dataset.msgRead = 'unread';
|
||||||
|
unreadElem.hidden = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSent = () => entry.dataset.msgSent === 'sent';
|
||||||
|
const setSent = state => {
|
||||||
|
entry.dataset.msgRead = state ? 'sent' : 'draft';
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkbox = entry.querySelector('.js-entry-checkbox');
|
||||||
|
const getSelected = () => checkbox.checked;
|
||||||
|
const setSelected = state => checkbox.checked = state;
|
||||||
|
const toggleSelected = () => checkbox.checked = !checkbox.checked;
|
||||||
|
|
||||||
|
let clickIsSelect = false;
|
||||||
|
|
||||||
|
const watchers = new MszWatchers;
|
||||||
|
watchers.define(['select']);
|
||||||
|
|
||||||
|
checkbox.addEventListener('click', ev => ev.stopPropagation());
|
||||||
|
checkbox.addEventListener('keydown', ev => ev.stopPropagation());
|
||||||
|
|
||||||
|
checkbox.addEventListener('change', () => {
|
||||||
|
watchers.call('select', getSelected());
|
||||||
|
});
|
||||||
|
|
||||||
|
const navigateToMessage = () => {
|
||||||
|
const url = entry.dataset.msgUrl;
|
||||||
|
if(url !== undefined && url.startsWith('/') && !url.startsWith('//'))
|
||||||
|
location.assign(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
entry.addEventListener('keydown', ev => {
|
||||||
|
if(ev.key === 'Enter' || ev.key === 'NumpadEnter') {
|
||||||
|
ev.preventDefault();
|
||||||
|
entry.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
entry.addEventListener('click', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if(clickIsSelect)
|
||||||
|
checkbox.click();
|
||||||
|
else
|
||||||
|
navigateToMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
entry.addEventListener('dblclick', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if(clickIsSelect)
|
||||||
|
navigateToMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
getId: () => msgId,
|
||||||
|
getElement: () => entry,
|
||||||
|
isRead: isRead,
|
||||||
|
setRead: setRead,
|
||||||
|
isSent: isSent,
|
||||||
|
setSent: setSent,
|
||||||
|
getSelected: getSelected,
|
||||||
|
setSelected: setSelected,
|
||||||
|
toggleSelected: toggleSelected,
|
||||||
|
setClickIsSelect: state => clickIsSelect = state,
|
||||||
|
onSelectedChange: handler => {
|
||||||
|
watchers.watch('select', handler, getSelected());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
386
assets/misuzu.js/messages/messages.js
Normal file
386
assets/misuzu.js/messages/messages.js
Normal file
|
@ -0,0 +1,386 @@
|
||||||
|
#include csrfp.js
|
||||||
|
#include msgbox.js
|
||||||
|
#include utility.js
|
||||||
|
#include messages/actbtn.js
|
||||||
|
#include messages/list.js
|
||||||
|
#include messages/recipient.js
|
||||||
|
#include messages/reply.jsx
|
||||||
|
#include messages/thread.js
|
||||||
|
|
||||||
|
const MszMessages = () => {
|
||||||
|
const extractMsgIds = msg => {
|
||||||
|
if(typeof msg.getId === 'function')
|
||||||
|
return msg.getId();
|
||||||
|
if(typeof msg.toString === 'function')
|
||||||
|
return msg.toString();
|
||||||
|
throw 'unsupported message type';
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayErrorMessage = async error => {
|
||||||
|
let text;
|
||||||
|
if(typeof error === 'string')
|
||||||
|
text = error;
|
||||||
|
else if(typeof error.text === 'string')
|
||||||
|
text = error.text;
|
||||||
|
else if(typeof error.toString === 'function')
|
||||||
|
text = error.toString();
|
||||||
|
else
|
||||||
|
text = 'Something indescribable happened.';
|
||||||
|
|
||||||
|
await MszShowMessageBox(text, 'Error');
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const msgsCreate = async (title, text, parser, draft, recipient, replyTo) => {
|
||||||
|
const formData = new FormData;
|
||||||
|
formData.append('_csrfp', MszCSRFP.getToken());
|
||||||
|
formData.append('title', title);
|
||||||
|
formData.append('body', text);
|
||||||
|
formData.append('parser', parser);
|
||||||
|
formData.append('draft', draft);
|
||||||
|
formData.append('recipient', recipient);
|
||||||
|
formData.append('reply', replyTo);
|
||||||
|
|
||||||
|
const result = await $x.post('/messages/create', { type: 'json' }, formData);
|
||||||
|
|
||||||
|
MszCSRFP.setFromHeaders(result);
|
||||||
|
|
||||||
|
const body = result.body();
|
||||||
|
if(body.error !== undefined)
|
||||||
|
throw body.error;
|
||||||
|
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
|
||||||
|
const msgsUpdate = async (messageId, title, text, parser, draft) => {
|
||||||
|
const formData = new FormData;
|
||||||
|
formData.append('_csrfp', MszCSRFP.getToken());
|
||||||
|
formData.append('title', title);
|
||||||
|
formData.append('body', text);
|
||||||
|
formData.append('parser', parser);
|
||||||
|
formData.append('draft', draft);
|
||||||
|
|
||||||
|
const result = await $x.post(`/messages/${encodeURIComponent(messageId)}`, { type: 'json' }, formData);
|
||||||
|
|
||||||
|
MszCSRFP.setFromHeaders(result);
|
||||||
|
|
||||||
|
const body = result.body();
|
||||||
|
if(body.error !== undefined)
|
||||||
|
throw body.error;
|
||||||
|
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
|
||||||
|
const msgsMark = async (msgs, state) => {
|
||||||
|
const result = await $x.post('/messages/mark', { type: 'json' }, {
|
||||||
|
_csrfp: MszCSRFP.getToken(),
|
||||||
|
type: state,
|
||||||
|
messages: msgs.map(extractMsgIds).join(','),
|
||||||
|
});
|
||||||
|
|
||||||
|
MszCSRFP.setFromHeaders(result);
|
||||||
|
|
||||||
|
const body = result.body();
|
||||||
|
if(body.error !== undefined)
|
||||||
|
throw body.error;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const msgsDelete = async msgs => {
|
||||||
|
const result = await $x.post('/messages/delete', { type: 'json' }, {
|
||||||
|
_csrfp: MszCSRFP.getToken(),
|
||||||
|
messages: msgs.map(extractMsgIds).join(','),
|
||||||
|
});
|
||||||
|
|
||||||
|
MszCSRFP.setFromHeaders(result);
|
||||||
|
|
||||||
|
const body = result.body();
|
||||||
|
if(body.error !== undefined)
|
||||||
|
throw body.error;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const msgsRestore = async msgs => {
|
||||||
|
const result = await $x.post('/messages/restore', { type: 'json' }, {
|
||||||
|
_csrfp: MszCSRFP.getToken(),
|
||||||
|
messages: msgs.map(extractMsgIds).join(','),
|
||||||
|
});
|
||||||
|
|
||||||
|
MszCSRFP.setFromHeaders(result);
|
||||||
|
|
||||||
|
const body = result.body();
|
||||||
|
if(body.error !== undefined)
|
||||||
|
throw body.error;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const msgsNuke = async msgs => {
|
||||||
|
const result = await $x.post('/messages/nuke', { type: 'json' }, {
|
||||||
|
_csrfp: MszCSRFP.getToken(),
|
||||||
|
messages: msgs.map(extractMsgIds).join(','),
|
||||||
|
});
|
||||||
|
|
||||||
|
MszCSRFP.setFromHeaders(result);
|
||||||
|
|
||||||
|
const body = result.body();
|
||||||
|
if(body.error !== undefined)
|
||||||
|
throw body.error;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const msgsUserBtns = Array.from($qa('.js-header-pms-button'));
|
||||||
|
if(msgsUserBtns.length > 0)
|
||||||
|
$x.get('/messages/stats', { type: 'json' }).then(result => {
|
||||||
|
const body = result.body();
|
||||||
|
if(typeof body === 'object' && typeof body.unread === 'number')
|
||||||
|
if(body.unread > 0)
|
||||||
|
for(const msgsUserBtn of msgsUserBtns)
|
||||||
|
msgsUserBtn.append($e({ child: body.unread.toLocaleString(), attrs: { className: 'header__desktop__user__button__count' } }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const msgsListElem = $q('.js-messages-list');
|
||||||
|
const msgsList = msgsListElem instanceof Element ? new MsgMessagesList(msgsListElem) : undefined;
|
||||||
|
|
||||||
|
const msgsListEmptyNotice = $q('.js-messages-folder-empty');
|
||||||
|
|
||||||
|
const msgsThreadElem = $q('.js-messages-thread');
|
||||||
|
const msgsThread = msgsThreadElem instanceof Element ? new MszMessagesThread(msgsThreadElem) : undefined;
|
||||||
|
|
||||||
|
const msgsRecipientElem = $q('.js-messages-recipient');
|
||||||
|
const msgsRecipient = msgsRecipientElem instanceof Element ? new MszMessagesRecipient(msgsRecipientElem) : undefined;
|
||||||
|
|
||||||
|
const msgsReplyElem = $q('.js-messages-reply');
|
||||||
|
const msgsReply = msgsReplyElem instanceof Element ? new MszMessagesReply(msgsReplyElem) : undefined;
|
||||||
|
|
||||||
|
if(msgsReply !== undefined) {
|
||||||
|
if(msgsRecipient !== undefined)
|
||||||
|
msgsRecipient.onUpdate(async info => {
|
||||||
|
msgsReply.setRecipient(typeof info.id === 'string' ? info.id : '');
|
||||||
|
msgsReply.setWarning(info.ban ? `${(typeof info.name === 'string' ? info.name : 'This user')} has been banned and will be unable to respond to your messages.` : undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
msgsReply.onSubmit(async form => {
|
||||||
|
try {
|
||||||
|
let result;
|
||||||
|
if(typeof form.message === 'string') {
|
||||||
|
result = await msgsUpdate(
|
||||||
|
form.message,
|
||||||
|
form.title,
|
||||||
|
form.body,
|
||||||
|
form.parser,
|
||||||
|
form.draft
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
result = await msgsCreate(
|
||||||
|
form.title,
|
||||||
|
form.body,
|
||||||
|
form.parser,
|
||||||
|
form.draft,
|
||||||
|
form.recipient,
|
||||||
|
form.reply || ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof result.url === 'string')
|
||||||
|
location.assign(result.url);
|
||||||
|
} catch(ex) {
|
||||||
|
return await displayErrorMessage(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let actSelectAll, actMarkRead, actMoveTrash, actNuke;
|
||||||
|
|
||||||
|
const actSelectAllBtn = $q('.js-messages-actions-select-all');
|
||||||
|
if(actSelectAllBtn instanceof Element) {
|
||||||
|
actSelectAll = new MszMessagesActionButton(actSelectAllBtn);
|
||||||
|
|
||||||
|
if(msgsList !== undefined) {
|
||||||
|
actSelectAll.setAction(async state => {
|
||||||
|
msgsList.setAllSelected(!state);
|
||||||
|
return !state;
|
||||||
|
});
|
||||||
|
msgsList.onSelectedChange((selectedNo, itemNo) => {
|
||||||
|
actSelectAll.setState(selectedNo >= itemNo);
|
||||||
|
});
|
||||||
|
actSelectAll.setState(msgsList.getAllSelected());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actMarkReadBtn = $q('.js-messages-actions-mark-read');
|
||||||
|
if(actMarkReadBtn instanceof Element) {
|
||||||
|
actMarkRead = new MszMessagesActionButton(actMarkReadBtn);
|
||||||
|
|
||||||
|
if(msgsList !== undefined) {
|
||||||
|
msgsList.onSelectedChange(selectedNo => {
|
||||||
|
const enabled = selectedNo > 0;
|
||||||
|
actMarkRead.setEnabled(enabled);
|
||||||
|
|
||||||
|
if(enabled) {
|
||||||
|
const items = msgsList.getSelectedItems();
|
||||||
|
let readNo = 0, unreadNo = 0;
|
||||||
|
|
||||||
|
for(const item of items) {
|
||||||
|
if(item.isRead())
|
||||||
|
++readNo;
|
||||||
|
else
|
||||||
|
++unreadNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
actMarkRead.setState(readNo > unreadNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
actMarkRead.setAction(async state => {
|
||||||
|
const items = msgsList.getSelectedItems();
|
||||||
|
|
||||||
|
const result = await actMarkRead.disableWith(async () => {
|
||||||
|
try {
|
||||||
|
return await msgsMark(items, state ? 'unread' : 'read');
|
||||||
|
} catch(ex) {
|
||||||
|
return await displayErrorMessage(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(result) {
|
||||||
|
state = !state;
|
||||||
|
|
||||||
|
for(const item of items)
|
||||||
|
item.setRead(state);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if(msgsThread !== undefined) {
|
||||||
|
actMarkRead.setAction(async state => {
|
||||||
|
const items = [msgsThread.getMessage()];
|
||||||
|
|
||||||
|
const result = await actMarkRead.disableWith(async () => {
|
||||||
|
try {
|
||||||
|
return await msgsMark(items, state ? 'unread' : 'read');
|
||||||
|
} catch(ex) {
|
||||||
|
return await displayErrorMessage(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result ? !state : state;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actMoveTrashBtn = $q('.js-messages-actions-move-trash');
|
||||||
|
if(actMoveTrashBtn instanceof Element) {
|
||||||
|
actMoveTrash = new MszMessagesActionButton(actMoveTrashBtn);
|
||||||
|
|
||||||
|
if(msgsList !== undefined) {
|
||||||
|
msgsList.onSelectedChange(selectedNo => actMoveTrash.setEnabled(selectedNo > 0));
|
||||||
|
actMoveTrash.setAction(async state => {
|
||||||
|
const items = msgsList.getSelectedItems();
|
||||||
|
|
||||||
|
if(!state && !await MszShowConfirmBox(`Are you sure you wish to delete ${items.length} item${items.length === 1 ? '' : 's'}?`, 'Confirmation'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const result = await actMoveTrash.disableWith(async () => {
|
||||||
|
try {
|
||||||
|
if(state)
|
||||||
|
return await msgsRestore(items);
|
||||||
|
return await msgsDelete(items);
|
||||||
|
} catch(ex) {
|
||||||
|
return await displayErrorMessage(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(result)
|
||||||
|
for(const message of items)
|
||||||
|
msgsList.removeItem(message);
|
||||||
|
|
||||||
|
if(msgsListEmptyNotice instanceof Element)
|
||||||
|
msgsListEmptyNotice.hidden = msgsList.getItemsCount() > 0;
|
||||||
|
});
|
||||||
|
} else if(msgsThread !== undefined) {
|
||||||
|
actMoveTrash.setAction(async state => {
|
||||||
|
if(!state && !await MszShowConfirmBox('Are you sure you wish to delete this message?', 'Confirmation'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const items = [msgsThread.getMessage()];
|
||||||
|
|
||||||
|
const result = await actMoveTrash.disableWith(async () => {
|
||||||
|
try {
|
||||||
|
if(state)
|
||||||
|
return await msgsRestore(items);
|
||||||
|
return await msgsDelete(items);
|
||||||
|
} catch(ex) {
|
||||||
|
return await displayErrorMessage(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(result) {
|
||||||
|
state = !state;
|
||||||
|
|
||||||
|
if(msgsReply !== undefined)
|
||||||
|
msgsReply.setHidden(state);
|
||||||
|
|
||||||
|
const msg = msgsThread.getMessage();
|
||||||
|
if(msg !== undefined)
|
||||||
|
msg.setDeleted(state);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actNukeBtn = $q('.js-messages-actions-nuke');
|
||||||
|
if(actNukeBtn instanceof Element) {
|
||||||
|
actNuke = new MszMessagesActionButton(actNukeBtn, true);
|
||||||
|
|
||||||
|
if(msgsList !== undefined) {
|
||||||
|
msgsList.onSelectedChange(selectedNo => actNuke.setEnabled(selectedNo > 0));
|
||||||
|
actNuke.setAction(async () => {
|
||||||
|
const items = msgsList.getSelectedItems();
|
||||||
|
|
||||||
|
if(!await MszShowConfirmBox(`Are you sure you wish to PERMANENTLY delete ${items.length} item${items.length === 1 ? '' : 's'}?`, 'Confirmation'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const result = await actNuke.disableWith(async () => {
|
||||||
|
try {
|
||||||
|
return await msgsNuke(items);
|
||||||
|
} catch(ex) {
|
||||||
|
return await displayErrorMessage(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(result)
|
||||||
|
for(const message of items)
|
||||||
|
msgsList.removeItem(message);
|
||||||
|
|
||||||
|
if(msgsListEmptyNotice instanceof Element)
|
||||||
|
msgsListEmptyNotice.hidden = msgsList.getItemsCount() > 0;
|
||||||
|
});
|
||||||
|
} else if(msgsThread !== undefined) {
|
||||||
|
actMoveTrash.watchState(state => {
|
||||||
|
actNuke.setHidden(!state);
|
||||||
|
});
|
||||||
|
actNuke.setAction(async () => {
|
||||||
|
if(!await MszShowConfirmBox('Are you sure you wish to PERMANENTLY delete this message?', 'Confirmation'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const items = [msgsThread.getMessage()];
|
||||||
|
|
||||||
|
const result = await actNuke.disableWith(async () => {
|
||||||
|
try {
|
||||||
|
return await msgsNuke(items);
|
||||||
|
} catch(ex) {
|
||||||
|
return await displayErrorMessage(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(result)
|
||||||
|
location.assign('/messages');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
56
assets/misuzu.js/messages/recipient.js
Normal file
56
assets/misuzu.js/messages/recipient.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#include csrfp.js
|
||||||
|
#include utility.js
|
||||||
|
|
||||||
|
const MszMessagesRecipient = function(element) {
|
||||||
|
if(!(element instanceof Element))
|
||||||
|
throw 'element must be an instance of Element';
|
||||||
|
|
||||||
|
const avatarElem = element.querySelector('.js-messages-recipient-avatar img');
|
||||||
|
const nameInput = element.querySelector('.js-messages-recipient-name');
|
||||||
|
|
||||||
|
let updateHandler = undefined;
|
||||||
|
const update = async () => {
|
||||||
|
const result = await $x.post(element.dataset.msgLookup, { type: 'json' }, {
|
||||||
|
_csrfp: MszCSRFP.getToken(),
|
||||||
|
name: nameInput.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
MszCSRFP.setFromHeaders(result);
|
||||||
|
|
||||||
|
const body = result.body();
|
||||||
|
|
||||||
|
if(updateHandler !== undefined)
|
||||||
|
await updateHandler(body);
|
||||||
|
|
||||||
|
if(typeof body.avatar === 'string')
|
||||||
|
avatarElem.src = body.avatar;
|
||||||
|
|
||||||
|
if(typeof body.name === 'string')
|
||||||
|
nameInput.value = body.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
let nameTimeout = null;
|
||||||
|
nameInput.addEventListener('input', () => {
|
||||||
|
if(nameTimeout !== undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
nameTimeout = setTimeout(() => {
|
||||||
|
update().finally(() => {
|
||||||
|
clearTimeout(nameTimeout);
|
||||||
|
nameTimeout = undefined;
|
||||||
|
});
|
||||||
|
}, 750);
|
||||||
|
});
|
||||||
|
|
||||||
|
update().finally(() => nameTimeout = undefined);
|
||||||
|
|
||||||
|
return {
|
||||||
|
getElement: () => element,
|
||||||
|
onUpdate: handler => {
|
||||||
|
if(typeof handler !== 'function')
|
||||||
|
throw 'handler must be a function';
|
||||||
|
|
||||||
|
updateHandler = handler;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
171
assets/misuzu.js/messages/reply.jsx
Normal file
171
assets/misuzu.js/messages/reply.jsx
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
#include parsing.js
|
||||||
|
#include ext/eeprom.js
|
||||||
|
|
||||||
|
const MszMessagesReply = function(element) {
|
||||||
|
if(!(element instanceof Element))
|
||||||
|
throw 'element must be an Element';
|
||||||
|
|
||||||
|
const form = element.querySelector('.js-messages-reply-form');
|
||||||
|
const bodyElem = form.querySelector('.js-messages-reply-body');
|
||||||
|
const actsElem = form.querySelector('.js-messages-reply-actions');
|
||||||
|
const parserSelect = form.querySelector('.js-messages-reply-parser');
|
||||||
|
const saveBtn = form.querySelector('.js-messages-reply-save');
|
||||||
|
const sendBtn = form.querySelector('.js-messages-reply-send');
|
||||||
|
const warnElem = form.querySelector('.js-reply-form-warning');
|
||||||
|
const warnText = warnElem instanceof Element ? warnElem.querySelector('.js-reply-form-warning-text') : undefined;
|
||||||
|
|
||||||
|
let submitHandler;
|
||||||
|
form.addEventListener('submit', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if(typeof submitHandler === 'function') {
|
||||||
|
const fields = Array.from(form.elements);
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
for(const field of fields) {
|
||||||
|
if((field instanceof HTMLButtonElement || (field instanceof HTMLInputElement && field.type === 'submit')) && ev.submitter !== field)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(typeof field.name === 'string' && field.name.length > 0)
|
||||||
|
result[field.name] = field.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitHandler(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bodyElem.addEventListener('keydown', ev => {
|
||||||
|
if((ev.code === 'Enter' || ev.code === 'NumpadEnter') && ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if(ev.shiftKey)
|
||||||
|
saveBtn.click();
|
||||||
|
else
|
||||||
|
sendBtn.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const switchButtons = parser => {
|
||||||
|
$rc(actsElem);
|
||||||
|
|
||||||
|
const tags = MszParsing.getTagsFor(parser);
|
||||||
|
actsElem.hidden = tags.length < 1;
|
||||||
|
for(const tag of tags)
|
||||||
|
actsElem.appendChild(<button class="messages-reply-action" type="button" title={tag.summary} onclick={() => $insertTags(bodyElem, tag.open, tag.close)}>
|
||||||
|
<i class={tag.icon}/>
|
||||||
|
</button>);
|
||||||
|
};
|
||||||
|
|
||||||
|
switchButtons(parserSelect.value);
|
||||||
|
|
||||||
|
parserSelect.addEventListener('change', () => {
|
||||||
|
switchButtons(parserSelect.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// this implementation is godawful but it'll do for now lol
|
||||||
|
// need to make it easier to share the forum's implementation
|
||||||
|
MszEEPROM.init()
|
||||||
|
.catch(() => console.error('Failed to initialise EEPROM'))
|
||||||
|
.then(() => {
|
||||||
|
const eepromClient = new EEPROM(peepApp, peepPath);
|
||||||
|
const eepromHandleFileUpload = async file => {
|
||||||
|
const uploadTask = eepromClient.create(file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileInfo = await uploadTask.start();
|
||||||
|
const parserMode = parseInt(parserSelect.value);
|
||||||
|
let insertText = location.protocol + fileInfo.url;
|
||||||
|
|
||||||
|
if(parserMode == 1) { // bbcode
|
||||||
|
if(fileInfo.isImage())
|
||||||
|
insertText = `[img]${fileInfo.url}[/img]`;
|
||||||
|
else if(fileInfo.isAudio())
|
||||||
|
insertText = `[audio]${fileInfo.url}[/audio]`;
|
||||||
|
else if(fileInfo.isVideo())
|
||||||
|
insertText = `[video]${fileInfo.url}[/video]`;
|
||||||
|
} else if(parserMode == 2) { // markdown
|
||||||
|
if(fileInfo.isMedia())
|
||||||
|
insertText = `![](${fileInfo.url})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertTags(bodyElem, insertText, '');
|
||||||
|
bodyElem.value = bodyElem.value.trim();
|
||||||
|
} catch(ex) {
|
||||||
|
let errorText = 'Upload aborted.';
|
||||||
|
|
||||||
|
if(!ex.aborted) {
|
||||||
|
console.error(ex);
|
||||||
|
errorText = ex.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
await MszShowMessageBox(errorText, 'Upload Error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bodyElem.addEventListener('paste', ev => {
|
||||||
|
if(ev.clipboardData && ev.clipboardData.files.length > 0) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
const files = ev.clipboardData.files;
|
||||||
|
for(const file of files)
|
||||||
|
eepromHandleFileUpload(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.addEventListener('dragenter', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
});
|
||||||
|
document.body.addEventListener('dragover', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
});
|
||||||
|
document.body.addEventListener('dragleave', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
});
|
||||||
|
document.body.addEventListener('drop', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
if(ev.dataTransfer && ev.dataTransfer.files.length > 0) {
|
||||||
|
const files = ev.dataTransfer.files;
|
||||||
|
for(const file of files)
|
||||||
|
eepromHandleFileUpload(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
getElement: () => element,
|
||||||
|
setWarning: text => {
|
||||||
|
if(warnElem === undefined || warnText === undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(text === undefined) {
|
||||||
|
warnElem.hidden = true;
|
||||||
|
warnText.textContent = '';
|
||||||
|
} else {
|
||||||
|
warnElem.hidden = false;
|
||||||
|
warnText.textContent = text;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setRecipient: userId => {
|
||||||
|
for(const field of form.elements)
|
||||||
|
if(field.name === 'recipient') {
|
||||||
|
field.value = userId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getHidden: () => element.hidden,
|
||||||
|
setHidden: state => {
|
||||||
|
element.hidden = state;
|
||||||
|
},
|
||||||
|
onSubmit: handler => {
|
||||||
|
if(typeof handler !== 'function')
|
||||||
|
throw 'handler must be a function';
|
||||||
|
|
||||||
|
submitHandler = handler;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
78
assets/misuzu.js/messages/thread.js
Normal file
78
assets/misuzu.js/messages/thread.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
const MszMessagesThread = function(thread) {
|
||||||
|
if(!(thread instanceof Element))
|
||||||
|
throw 'thread must be an element';
|
||||||
|
|
||||||
|
const messages = Array.from(thread.querySelectorAll('.js-messages-message')).map(elem => new MszMessagesThreadMessage(elem));
|
||||||
|
const message = messages.find(msg => msg.isFull());
|
||||||
|
|
||||||
|
return {
|
||||||
|
getMessage: () => message,
|
||||||
|
getMessages: () => messages,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const MszMessagesThreadMessage = function(message) {
|
||||||
|
if(!(message instanceof Element))
|
||||||
|
throw 'message must be an element';
|
||||||
|
|
||||||
|
const msgId = message.dataset.msgId;
|
||||||
|
const type = message.dataset.msgType;
|
||||||
|
const url = message.dataset.msgUrl;
|
||||||
|
|
||||||
|
if(type === 'snip') {
|
||||||
|
message.addEventListener('click', ev => {
|
||||||
|
if(typeof url !== 'string')
|
||||||
|
return;
|
||||||
|
|
||||||
|
let target = ev.target;
|
||||||
|
while(target !== message) {
|
||||||
|
if(target instanceof HTMLAnchorElement)
|
||||||
|
return;
|
||||||
|
|
||||||
|
target = target.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
location.assign(url);
|
||||||
|
});
|
||||||
|
} else if(type === 'full') {
|
||||||
|
message.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRead = () => message.dataset.msgRead === 'read';
|
||||||
|
const setRead = state => {
|
||||||
|
message.dataset.msgRead = state ? 'read' : 'unread';
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSent = () => message.dataset.msgSent === 'sent';
|
||||||
|
const setSent = state => {
|
||||||
|
message.dataset.msgRead = state ? 'sent' : 'draft';
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDeleted = () => message.dataset.msgDeleted === 'yes';
|
||||||
|
const setDeleted = state => {
|
||||||
|
if(state) {
|
||||||
|
message.dataset.msgDeleted = 'yes';
|
||||||
|
message.classList.add('messages-message-deleted');
|
||||||
|
} else {
|
||||||
|
message.dataset.msgDeleted = 'no';
|
||||||
|
message.classList.remove('messages-message-deleted');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getId: () => msgId,
|
||||||
|
getType: () => type,
|
||||||
|
isFull: () => type === 'full',
|
||||||
|
isSnippet: () => type === 'snip',
|
||||||
|
isRead: isRead,
|
||||||
|
setRead: setRead,
|
||||||
|
isSent: isSent,
|
||||||
|
setSent: setSent,
|
||||||
|
isDeleted: isDeleted,
|
||||||
|
setDeleted: setDeleted,
|
||||||
|
};
|
||||||
|
};
|
73
assets/misuzu.js/msgbox.jsx
Normal file
73
assets/misuzu.js/msgbox.jsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#include utility.js
|
||||||
|
|
||||||
|
const MszShowConfirmBox = async (text, title, target) => {
|
||||||
|
let result = false;
|
||||||
|
|
||||||
|
await MszShowMessageBox(text, title, [
|
||||||
|
{ text: 'Yes', callback: async () => result = true },
|
||||||
|
{ text: 'No' },
|
||||||
|
], target);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MszShowMessageBox = (text, title, buttons, target) => {
|
||||||
|
if(typeof text !== 'string') {
|
||||||
|
if(text !== undefined && text !== null && typeof text.toString === 'function')
|
||||||
|
text = text.toString();
|
||||||
|
else throw 'text must be a string';
|
||||||
|
}
|
||||||
|
if(!(target instanceof Element))
|
||||||
|
target = document.body;
|
||||||
|
|
||||||
|
if(typeof title !== 'string')
|
||||||
|
title = 'Information';
|
||||||
|
if(!Array.isArray(buttons))
|
||||||
|
buttons = [];
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(target.querySelector('.messagebox')) {
|
||||||
|
reject();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttonsElem;
|
||||||
|
const html = <div class="messagebox">
|
||||||
|
<div class="container messagebox__container">
|
||||||
|
<div class="container__title">
|
||||||
|
<div class="container__title__background"/>
|
||||||
|
<div class="container__title__text">{title}</div>
|
||||||
|
</div>
|
||||||
|
<div class="container__content">{text}</div>
|
||||||
|
{buttonsElem = <div class="messagebox__buttons"/>}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
let firstButton;
|
||||||
|
if(buttons.length < 1) {
|
||||||
|
firstButton = <button class="input__button" onclick={() => {
|
||||||
|
html.remove();
|
||||||
|
resolve();
|
||||||
|
}}>OK</button>;
|
||||||
|
buttonsElem.appendChild(firstButton);
|
||||||
|
} else {
|
||||||
|
for(const button of buttons) {
|
||||||
|
const buttonElem = <button class="input__button" onclick={() => {
|
||||||
|
html.remove();
|
||||||
|
|
||||||
|
if(typeof button.callback === 'function')
|
||||||
|
button.callback().finally(() => resolve());
|
||||||
|
else
|
||||||
|
resolve();
|
||||||
|
}}>{button.text}</button>;
|
||||||
|
buttonsElem.appendChild(buttonElem);
|
||||||
|
|
||||||
|
if(firstButton === undefined)
|
||||||
|
firstButton = buttonElem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target.appendChild(html);
|
||||||
|
firstButton.focus();
|
||||||
|
});
|
||||||
|
};
|
56
assets/misuzu.js/parsing.js
Normal file
56
assets/misuzu.js/parsing.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// welcome to the shitty temporary file for managing the bbcode/markdown/whatever button
|
||||||
|
const MszParsing = (() => {
|
||||||
|
const defineTag = (name, open, close, summary, icon) => {
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
open: open,
|
||||||
|
close: close,
|
||||||
|
summary: summary,
|
||||||
|
icon: icon,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const bbTags = [
|
||||||
|
defineTag('bb-bold', '[b]', '[/b]', 'Bold [b]<text>[/b]', 'fas fa-bold fa-fw'),
|
||||||
|
defineTag('bb-italic', '[i]', '[/i]', 'Italic [i]<text>[/i]', 'fas fa-italic fa-fw'),
|
||||||
|
defineTag('bb-underline', '[u]', '[/u]', 'Underline [u]<text>[/u]', 'fas fa-underline fa-fw'),
|
||||||
|
defineTag('bb-strike', '[s]', '[/s]', 'Strikethrough [s]<text>[/s]', 'fas fa-strikethrough fa-fw'),
|
||||||
|
defineTag('bb-link', '[url=]', '[/url]', 'Link [url]<url>[/url] or [url=<url>]<text>[/url]', 'fas fa-link fa-fw'),
|
||||||
|
defineTag('bb-image', '[img]', '[/img]', 'Image [img]<url>[/img]', 'fas fa-image fa-fw'),
|
||||||
|
defineTag('bb-audio', '[audio]', '[/audio]', 'Audio [audio]<url>[/audio]', 'fas fa-music fa-fw'),
|
||||||
|
defineTag('bb-video', '[video]', '[/video]', 'Video [video]<url>[/video]', 'fas fa-video fa-fw'),
|
||||||
|
defineTag('bb-code', '[code]', '[/code]', 'Code [code]<code>[/code]', 'fas fa-code fa-fw'),
|
||||||
|
defineTag('bb-zalgo', '[zalgo]', '[/zalgo]', 'Zalgo [zalgo]<text>[/zalgo]', 'fas fa-frog fa-fw'),
|
||||||
|
];
|
||||||
|
|
||||||
|
const mdTags = [
|
||||||
|
defineTag('md-bold', '**', '**', 'Bold **<text>**', 'fas fa-bold fa-fw'),
|
||||||
|
defineTag('md-italic', '*', '*', 'Italic *<text>* or _<text>_', 'fas fa-italic fa-fw'),
|
||||||
|
defineTag('md-underline', '__', '__', 'Underline __<text>__', 'fas fa-underline fa-fw'),
|
||||||
|
defineTag('md-strike', '~~', '~~', 'Strikethrough ~~<text>~~', 'fas fa-strikethrough fa-fw'),
|
||||||
|
defineTag('md-link', '[](', ')', 'Link [<text>](<url>)', 'fas fa-link fa-fw'),
|
||||||
|
defineTag('md-image', '![](', ')', 'Image ![<alt text>](<url>)', 'fas fa-image fa-fw'),
|
||||||
|
defineTag('md-audio', '![](', ')', 'Audio ![<alt text>](<url>)', 'fas fa-music fa-fw'),
|
||||||
|
defineTag('md-video', '![](', ')', 'Video ![<alt text>](<url>)', 'fas fa-video fa-fw'),
|
||||||
|
defineTag('md-code', '```', '```', 'Code `<code>` or ```<code>```', 'fas fa-code fa-fw'),
|
||||||
|
];
|
||||||
|
|
||||||
|
const getTagsFor = parser => {
|
||||||
|
if(typeof parser !== 'number')
|
||||||
|
parser = parseInt(parser);
|
||||||
|
|
||||||
|
if(parser === 1)
|
||||||
|
return bbTags;
|
||||||
|
if(parser === 2)
|
||||||
|
return mdTags;
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getTagsFor: getTagsFor,
|
||||||
|
getTagsForPlainText: () => getTagsFor(0),
|
||||||
|
getTagsForBBcode: () => getTagsFor(1),
|
||||||
|
getTagsForMarkdown: () => getTagsFor(2),
|
||||||
|
};
|
||||||
|
})();
|
|
@ -1,58 +0,0 @@
|
||||||
const Uiharu = function(apiUrl) {
|
|
||||||
const maxBatchSize = 4;
|
|
||||||
const lookupOneUrl = apiUrl + '/metadata',
|
|
||||||
lookupManyUrl = apiUrl + '/metadata/batch';
|
|
||||||
|
|
||||||
const lookupManyInternal = function(targetUrls, callback) {
|
|
||||||
const formData = new FormData;
|
|
||||||
|
|
||||||
for(const url of targetUrls)
|
|
||||||
formData.append('url[]', url);
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest;
|
|
||||||
xhr.addEventListener('load', function() {
|
|
||||||
callback(JSON.parse(xhr.responseText));
|
|
||||||
});
|
|
||||||
xhr.addEventListener('error', function(ev) {
|
|
||||||
callback({ status: xhr.status, error: 'xhr', details: ev });
|
|
||||||
});
|
|
||||||
xhr.open('POST', lookupManyUrl);
|
|
||||||
xhr.send(formData);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
lookupOne: function(targetUrl, callback) {
|
|
||||||
if(typeof callback !== 'function')
|
|
||||||
throw 'callback is missing';
|
|
||||||
targetUrl = (targetUrl || '').toString();
|
|
||||||
if(targetUrl.length < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest;
|
|
||||||
xhr.addEventListener('load', function() {
|
|
||||||
callback(JSON.parse(xhr.responseText));
|
|
||||||
});
|
|
||||||
xhr.addEventListener('error', function() {
|
|
||||||
callback({ status: xhr.status, error: 'xhr', details: ex });
|
|
||||||
});
|
|
||||||
xhr.open('POST', lookupOneUrl);
|
|
||||||
xhr.send(targetUrl);
|
|
||||||
},
|
|
||||||
lookupMany: function(targetUrls, callback) {
|
|
||||||
if(!Array.isArray(targetUrls))
|
|
||||||
throw 'targetUrls must be an array of urls';
|
|
||||||
if(typeof callback !== 'function')
|
|
||||||
throw 'callback is missing';
|
|
||||||
if(targetUrls < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(targetUrls.length <= maxBatchSize) {
|
|
||||||
lookupManyInternal(targetUrls, callback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let i = 0; i < targetUrls.length; i += maxBatchSize)
|
|
||||||
lookupManyInternal(targetUrls.slice(i, i + maxBatchSize), callback);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -13,6 +13,10 @@ const $ri = function(name) {
|
||||||
$r($i(name));
|
$r($i(name));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $rq = function(query) {
|
||||||
|
$r($q(query));
|
||||||
|
};
|
||||||
|
|
||||||
const $ib = function(ref, elem) {
|
const $ib = function(ref, elem) {
|
||||||
ref.parentNode.insertBefore(elem, ref);
|
ref.parentNode.insertBefore(elem, ref);
|
||||||
};
|
};
|
||||||
|
@ -79,6 +83,11 @@ const $e = function(info, attrs, child, created) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'boolean':
|
||||||
|
if(attr)
|
||||||
|
elem.setAttribute(key, '');
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if(key === 'className')
|
if(key === 'className')
|
||||||
key = 'class';
|
key = 'class';
|
||||||
|
@ -153,17 +162,125 @@ const $as = function(array) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var $insertTags = function(target, tagOpen, tagClose) {
|
const $x = (function() {
|
||||||
|
const send = function(method, url, options, body) {
|
||||||
|
if(options === undefined)
|
||||||
|
options = {};
|
||||||
|
else if(typeof options !== 'object')
|
||||||
|
throw 'options must be undefined or an object';
|
||||||
|
|
||||||
|
const xhr = new XMLHttpRequest;
|
||||||
|
const requestHeaders = new Map;
|
||||||
|
|
||||||
|
if('headers' in options && typeof options.headers === 'object')
|
||||||
|
for(const name in options.headers)
|
||||||
|
if(options.headers.hasOwnProperty(name))
|
||||||
|
requestHeaders.set(name.toLowerCase(), options.headers[name]);
|
||||||
|
|
||||||
|
if(typeof options.download === 'function') {
|
||||||
|
xhr.onloadstart = ev => options.download(ev);
|
||||||
|
xhr.onprogress = ev => options.download(ev);
|
||||||
|
xhr.onloadend = ev => options.download(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof options.upload === 'function') {
|
||||||
|
xhr.upload.onloadstart = ev => options.upload(ev);
|
||||||
|
xhr.upload.onprogress = ev => options.upload(ev);
|
||||||
|
xhr.upload.onloadend = ev => options.upload(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.authed)
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
|
||||||
|
if(typeof options.timeout === 'number')
|
||||||
|
xhr.timeout = options.timeout;
|
||||||
|
|
||||||
|
if(typeof options.type === 'string')
|
||||||
|
xhr.responseType = options.type;
|
||||||
|
|
||||||
|
if(typeof options.abort === 'function')
|
||||||
|
options.abort(() => xhr.abort());
|
||||||
|
|
||||||
|
if(typeof options.xhr === 'function')
|
||||||
|
options.xhr(() => xhr);
|
||||||
|
|
||||||
|
if(typeof body === 'object') {
|
||||||
|
if(body instanceof URLSearchParams) {
|
||||||
|
requestHeaders.set('content-type', 'application/x-www-form-urlencoded');
|
||||||
|
} else if(body instanceof FormData) {
|
||||||
|
// content-type is implicitly set
|
||||||
|
} else if(body instanceof Blob || body instanceof ArrayBuffer || body instanceof DataView) {
|
||||||
|
if(!requestHeaders.has('content-type'))
|
||||||
|
requestHeaders.set('content-type', 'application/octet-stream');
|
||||||
|
} else if(!requestHeaders.has('content-type')) {
|
||||||
|
const bodyParts = [];
|
||||||
|
for(const name in body)
|
||||||
|
if(body.hasOwnProperty(name))
|
||||||
|
bodyParts.push(encodeURIComponent(name) + '=' + encodeURIComponent(body[name]));
|
||||||
|
body = bodyParts.join('&');
|
||||||
|
requestHeaders.set('content-type', 'application/x-www-form-urlencoded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let responseHeaders = undefined;
|
||||||
|
|
||||||
|
xhr.onload = ev => resolve({
|
||||||
|
status: xhr.status,
|
||||||
|
body: () => xhr.response,
|
||||||
|
text: () => xhr.responseText,
|
||||||
|
headers: () => {
|
||||||
|
if(responseHeaders !== undefined)
|
||||||
|
return responseHeaders;
|
||||||
|
|
||||||
|
responseHeaders = new Map;
|
||||||
|
|
||||||
|
const raw = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);
|
||||||
|
for(const name in raw)
|
||||||
|
if(raw.hasOwnProperty(name)) {
|
||||||
|
const parts = raw[name].split(': ');
|
||||||
|
responseHeaders.set(parts.shift(), parts.join(': '));
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseHeaders;
|
||||||
|
},
|
||||||
|
xhr: xhr,
|
||||||
|
ev: ev,
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.onerror = ev => reject({
|
||||||
|
xhr: xhr,
|
||||||
|
ev: ev,
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.open(method, url);
|
||||||
|
for(const [name, value] of requestHeaders)
|
||||||
|
xhr.setRequestHeader(name, value);
|
||||||
|
xhr.send(body);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
send: send,
|
||||||
|
get: (url, options, body) => send('GET', url, options, body),
|
||||||
|
post: (url, options, body) => send('POST', url, options, body),
|
||||||
|
delete: (url, options, body) => send('DELETE', url, options, body),
|
||||||
|
patch: (url, options, body) => send('PATCH', url, options, body),
|
||||||
|
put: (url, options, body) => send('PUT', url, options, body),
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
const $insertTags = function(target, tagOpen, tagClose) {
|
||||||
tagOpen = tagOpen || '';
|
tagOpen = tagOpen || '';
|
||||||
tagClose = tagClose || '';
|
tagClose = tagClose || '';
|
||||||
|
|
||||||
if(document.selection) {
|
if(document.selection) {
|
||||||
target.focus();
|
target.focus();
|
||||||
var selected = document.selection.createRange();
|
const selected = document.selection.createRange();
|
||||||
selected.text = tagOpen + selected.text + tagClose;
|
selected.text = tagOpen + selected.text + tagClose;
|
||||||
target.focus();
|
target.focus();
|
||||||
} else if(target.selectionStart || target.selectionStart === 0) {
|
} else if(target.selectionStart || target.selectionStart === 0) {
|
||||||
var startPos = target.selectionStart,
|
const startPos = target.selectionStart,
|
||||||
endPos = target.selectionEnd,
|
endPos = target.selectionEnd,
|
||||||
scrollTop = target.scrollTop;
|
scrollTop = target.scrollTop;
|
||||||
|
|
||||||
|
@ -176,7 +293,7 @@ var $insertTags = function(target, tagOpen, tagClose) {
|
||||||
target.focus();
|
target.focus();
|
||||||
target.selectionStart = startPos + tagOpen.length;
|
target.selectionStart = startPos + tagOpen.length;
|
||||||
target.selectionEnd = endPos + tagOpen.length;
|
target.selectionEnd = endPos + tagOpen.length;
|
||||||
target.scrollTop + scrollTop;
|
target.scrollTop = scrollTop;
|
||||||
} else {
|
} else {
|
||||||
target.value += tagOpen + tagClose;
|
target.value += tagOpen + tagClose;
|
||||||
target.focus();
|
target.focus();
|
|
@ -1,83 +1,65 @@
|
||||||
const MszWatcher = function() {
|
const MszWatcher = function() {
|
||||||
let watchers = [];
|
const handlers = [];
|
||||||
|
|
||||||
return {
|
const watch = (handler, ...args) => {
|
||||||
watch: function(watcher, thisArg, args) {
|
if(typeof handler !== 'function')
|
||||||
if(typeof watcher !== 'function')
|
throw 'handler must be a function';
|
||||||
throw 'watcher must be a function';
|
if(handlers.includes(handler))
|
||||||
if(watchers.indexOf(watcher) >= 0)
|
throw 'handler already registered';
|
||||||
return;
|
|
||||||
|
|
||||||
watchers.push(watcher);
|
handlers.push(handler);
|
||||||
|
args.push(true);
|
||||||
if(thisArg !== undefined) {
|
handler(...args);
|
||||||
if(!Array.isArray(args)) {
|
|
||||||
if(args !== undefined)
|
|
||||||
args = [args];
|
|
||||||
else args = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// initial call
|
|
||||||
args.push(true);
|
|
||||||
|
|
||||||
watcher.apply(thisArg, args);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unwatch: function(watcher) {
|
|
||||||
$ari(watchers, watcher);
|
|
||||||
},
|
|
||||||
call: function(thisArg, args) {
|
|
||||||
if(!Array.isArray(args)) {
|
|
||||||
if(args !== undefined)
|
|
||||||
args = [args];
|
|
||||||
else args = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push(false);
|
|
||||||
|
|
||||||
for(const watcher of watchers)
|
|
||||||
watcher.apply(thisArg, args);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const MszWatcherCollection = function() {
|
|
||||||
const collection = new Map;
|
|
||||||
|
|
||||||
const watch = function(name, watcher, thisArg, args) {
|
|
||||||
const watchers = collection.get(name);
|
|
||||||
if(watchers === undefined)
|
|
||||||
throw 'undefined watcher name';
|
|
||||||
watchers.watch(watcher, thisArg, args);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const unwatch = function(name, watcher) {
|
const unwatch = handler => {
|
||||||
const watchers = collection.get(name);
|
$ari(handlers, handler);
|
||||||
if(watchers === undefined)
|
|
||||||
throw 'undefined watcher name';
|
|
||||||
watchers.unwatch(watcher);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
define: function(names) {
|
|
||||||
if(!Array.isArray(names))
|
|
||||||
names = [names];
|
|
||||||
for(const name of names)
|
|
||||||
collection.set(name, new MszWatcher);
|
|
||||||
},
|
|
||||||
call: function(name, thisArg, args) {
|
|
||||||
const watchers = collection.get(name);
|
|
||||||
if(watchers === undefined)
|
|
||||||
throw 'undefined watcher name';
|
|
||||||
watchers.call(thisArg, args);
|
|
||||||
},
|
|
||||||
watch: watch,
|
watch: watch,
|
||||||
unwatch: unwatch,
|
unwatch: unwatch,
|
||||||
proxy: function(obj) {
|
call: (...args) => {
|
||||||
obj.watch = function(name, watcher) {
|
args.push(false);
|
||||||
watch(name, watcher);
|
|
||||||
};
|
for(const handler of handlers)
|
||||||
obj.unwatch = unwatch;
|
handler(...args);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const MszWatchers = function() {
|
||||||
|
const watchers = new Map;
|
||||||
|
|
||||||
|
const getWatcher = name => {
|
||||||
|
const watcher = watchers.get(name);
|
||||||
|
if(watcher === undefined)
|
||||||
|
throw 'undefined watcher name';
|
||||||
|
return watcher;
|
||||||
|
};
|
||||||
|
|
||||||
|
const watch = (name, handler, ...args) => {
|
||||||
|
getWatcher(name).watch(handler, ...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
const unwatch = (name, handler) => {
|
||||||
|
getWatcher(name).unwatch(handler);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
watch: watch,
|
||||||
|
unwatch: unwatch,
|
||||||
|
define: names => {
|
||||||
|
if(typeof names === 'string')
|
||||||
|
watchers.set(names, new MszWatcher);
|
||||||
|
else if(Array.isArray(names))
|
||||||
|
for(const name of names)
|
||||||
|
watchers.set(name, new MszWatcher);
|
||||||
|
else
|
||||||
|
throw 'names must be an array of names or a single name';
|
||||||
|
},
|
||||||
|
call: (name, ...args) => {
|
||||||
|
getWatcher(name).call(...args);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
exports.strtr = (str, replacements) => str.toString().replace(
|
||||||
|
/{([^}]+)}/g, (match, key) => replacements[key] || match
|
||||||
|
);
|
||||||
|
|
||||||
const trim = function(str, chars, flags) {
|
const trim = function(str, chars, flags) {
|
||||||
if(chars === undefined)
|
if(chars === undefined)
|
||||||
chars = " \n\r\t\v\0";
|
chars = " \n\r\t\v\0";
|
||||||
|
|
146
build.js
146
build.js
|
@ -1,3 +1,4 @@
|
||||||
|
// IMPORTS
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const swc = require('@swc/core');
|
const swc = require('@swc/core');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
@ -6,25 +7,42 @@ const postcss = require('postcss');
|
||||||
const utils = require('./assets/utils.js');
|
const utils = require('./assets/utils.js');
|
||||||
const assproc = require('./assets/assproc.js');
|
const assproc = require('./assets/assproc.js');
|
||||||
|
|
||||||
|
|
||||||
|
// CONFIG
|
||||||
const rootDir = __dirname;
|
const rootDir = __dirname;
|
||||||
const modulesDir = path.join(rootDir, 'node_modules');
|
const srcDir = path.join(rootDir, 'assets');
|
||||||
|
const srcCurrentInfo = path.join(srcDir, 'current.json');
|
||||||
const assetsDir = path.join(rootDir, 'assets');
|
|
||||||
const assetsCSS = path.join(assetsDir, 'misuzu.css');
|
|
||||||
const assetsJS = path.join(assetsDir, 'misuzu.js');
|
|
||||||
const assetsInfo = path.join(assetsDir, 'current.json');
|
|
||||||
|
|
||||||
const pubDir = path.join(rootDir, 'public');
|
const pubDir = path.join(rootDir, 'public');
|
||||||
const pubIndex = path.join(pubDir, 'index.html');
|
const pubAssetsDir = path.join(pubDir, 'assets');
|
||||||
const pubAssets = '/assets';
|
|
||||||
const pubAssetsFull = path.join(pubDir, pubAssets);
|
|
||||||
const pubAssetCSSFormat = '%s-%s.css';
|
|
||||||
const pubAssetJSFormat = '%s-%s.js';
|
|
||||||
|
|
||||||
const isDebugBuild = fs.existsSync(path.join(rootDir, '.debug'));
|
const isDebugBuild = fs.existsSync(path.join(rootDir, '.debug'));
|
||||||
|
|
||||||
|
const buildTasks = {
|
||||||
|
js: [
|
||||||
|
{ source: 'misuzu.js', target: '/assets', name: 'misuzu.{hash}.js', },
|
||||||
|
],
|
||||||
|
css: [
|
||||||
|
{ source: 'misuzu.css', target: '/assets', name: 'misuzu.{hash}.css', },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// PREP
|
||||||
|
const postcssPlugins = [ require('autoprefixer')({ remove: false }) ];
|
||||||
|
if(!isDebugBuild)
|
||||||
|
postcssPlugins.push(require('cssnano')({
|
||||||
|
preset: [
|
||||||
|
'cssnano-preset-default',
|
||||||
|
{
|
||||||
|
minifyGradients: false,
|
||||||
|
reduceIdents: false,
|
||||||
|
zindex: true,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
const swcJscOptions = {
|
const swcJscOptions = {
|
||||||
target: 'es2016',
|
target: 'es2021',
|
||||||
loose: false,
|
loose: false,
|
||||||
externalHelpers: false,
|
externalHelpers: false,
|
||||||
keepClassNames: true,
|
keepClassNames: true,
|
||||||
|
@ -51,39 +69,89 @@ const swcJscOptions = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const postcssPlugins = [];
|
|
||||||
if(!isDebugBuild) postcssPlugins.push(require('cssnano'));
|
|
||||||
postcssPlugins.push(require('autoprefixer')({
|
|
||||||
remove: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
fs.mkdirSync(pubAssetsFull, { recursive: true });
|
|
||||||
|
|
||||||
|
// BUILD
|
||||||
(async () => {
|
(async () => {
|
||||||
const mszCssName = await assproc.process(assetsCSS, { 'prefix': '@', 'entry': 'main.css' })
|
const files = {};
|
||||||
.then(output => postcss(postcssPlugins).process(output, { from: assetsCSS }).then(output => {
|
|
||||||
const mszCssName = path.join(pubAssets, util.format(pubAssetCSSFormat, 'misuzu', utils.shortHash(output.css)));
|
|
||||||
fs.writeFileSync(path.join(pubDir, mszCssName), output.css);
|
|
||||||
return mszCssName;
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mszJsName = await assproc.process(assetsJS, { 'prefix': '#', 'entry': 'main.js' })
|
console.log('Ensuring assets directory exists...');
|
||||||
.then(output => swc.transform(output, {
|
fs.mkdirSync(pubAssetsDir, { recursive: true });
|
||||||
filename: 'misuzu.js',
|
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
console.log('JS assets');
|
||||||
|
for(const info of buildTasks.js) {
|
||||||
|
console.log(`=> Building ${info.source}...`);
|
||||||
|
|
||||||
|
let origTarget = undefined;
|
||||||
|
if('es' in info) {
|
||||||
|
origTarget = swcJscOptions.target;
|
||||||
|
swcJscOptions.target = info.es;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assprocOpts = {
|
||||||
|
prefix: '#',
|
||||||
|
entry: info.entry || 'main.js',
|
||||||
|
};
|
||||||
|
const swcOpts = {
|
||||||
|
filename: info.source,
|
||||||
sourceMaps: false,
|
sourceMaps: false,
|
||||||
isModule: false,
|
isModule: false,
|
||||||
minify: !isDebugBuild,
|
minify: !isDebugBuild,
|
||||||
jsc: swcJscOptions,
|
jsc: swcJscOptions,
|
||||||
}).then(async output => {
|
};
|
||||||
const mszJsName = path.join(pubAssets, util.format(pubAssetJSFormat, 'misuzu', utils.shortHash(output.code)));
|
|
||||||
fs.writeFileSync(path.join(pubDir, mszJsName), output.code);
|
|
||||||
return mszJsName;
|
|
||||||
}));
|
|
||||||
|
|
||||||
fs.writeFileSync(assetsInfo, JSON.stringify({
|
const pubName = await assproc.process(path.join(srcDir, info.source), assprocOpts)
|
||||||
mszjs: mszJsName,
|
.then(output => swc.transform(output, swcOpts))
|
||||||
mszcss: mszCssName,
|
.then(output => {
|
||||||
}));
|
const name = utils.strtr(info.name, { hash: utils.shortHash(output.code) });
|
||||||
|
const pubName = path.join(info.target || '', name);
|
||||||
|
|
||||||
assproc.housekeep(pubAssetsFull);
|
console.log(` Saving to ${pubName}...`);
|
||||||
|
fs.writeFileSync(path.join(pubDir, pubName), output.code);
|
||||||
|
|
||||||
|
return pubName;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(origTarget !== undefined)
|
||||||
|
swcJscOptions.target = origTarget;
|
||||||
|
|
||||||
|
files[info.source] = pubName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
console.log('CSS assets');
|
||||||
|
for(const info of buildTasks.css) {
|
||||||
|
console.log(`=> Building ${info.source}...`);
|
||||||
|
|
||||||
|
const sourcePath = path.join(srcDir, info.source);
|
||||||
|
const assprocOpts = {
|
||||||
|
prefix: '@',
|
||||||
|
entry: info.entry || 'main.css',
|
||||||
|
};
|
||||||
|
const postcssOpts = { from: sourcePath };
|
||||||
|
|
||||||
|
files[info.source] = await assproc.process(sourcePath, assprocOpts)
|
||||||
|
.then(output => postcss(postcssPlugins).process(output, postcssOpts)
|
||||||
|
.then(output => {
|
||||||
|
const name = utils.strtr(info.name, { hash: utils.shortHash(output.css) });
|
||||||
|
const pubName = path.join(info.target || '', name);
|
||||||
|
|
||||||
|
console.log(` Saving to ${pubName}...`);
|
||||||
|
fs.writeFileSync(path.join(pubDir, pubName), output.css);
|
||||||
|
|
||||||
|
return pubName;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
console.log('Writing assets info...');
|
||||||
|
fs.writeFileSync(srcCurrentInfo, JSON.stringify(files));
|
||||||
|
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
console.log('Cleaning up old builds...');
|
||||||
|
assproc.housekeep(pubAssetsDir);
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"require": {
|
"require": {
|
||||||
"flashwave/index": "dev-master",
|
"flashwave/index": "dev-master",
|
||||||
"twig/twig": "^3.0",
|
"flashwave/sasae": "dev-master",
|
||||||
"erusev/parsedown": "~1.6",
|
"erusev/parsedown": "~1.6",
|
||||||
"chillerlan/php-qrcode": "^4.3",
|
"chillerlan/php-qrcode": "^4.3",
|
||||||
"symfony/mailer": "^6.0",
|
"symfony/mailer": "^6.0",
|
||||||
"matomo/device-detector": "^6.1"
|
"matomo/device-detector": "^6.1",
|
||||||
|
"sentry/sdk": "^4.0",
|
||||||
|
"flashwave/syokuhou": "dev-master"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"classmap": [
|
"classmap": [
|
||||||
|
@ -27,7 +29,8 @@
|
||||||
"preferred-install": "dist",
|
"preferred-install": "dist",
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"composer/installers": true,
|
"composer/installers": true,
|
||||||
"wikimedia/composer-merge-plugin": false
|
"wikimedia/composer-merge-plugin": false,
|
||||||
|
"php-http/discovery": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
|
1099
composer.lock
generated
1099
composer.lock
generated
File diff suppressed because it is too large
Load diff
8
config/config.example.cfg
Normal file
8
config/config.example.cfg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Example configuration for Misuzu
|
||||||
|
# and ; can be used at the start of a line for comments.
|
||||||
|
|
||||||
|
database:dsn mariadb://<user>:<pass>@<host>/<name>?charset=utf8mb4
|
||||||
|
|
||||||
|
;sentry:dsn https://sentry dsn here
|
||||||
|
;sentry:tracesRate 1.0
|
||||||
|
;sentry:profilesRate 1.0
|
|
@ -1,10 +0,0 @@
|
||||||
; Example configuration for Misuzu
|
|
||||||
|
|
||||||
[Database]
|
|
||||||
driver = mysql
|
|
||||||
host = localhost
|
|
||||||
port = 3306
|
|
||||||
username = username
|
|
||||||
password = password
|
|
||||||
dbname = database
|
|
||||||
charset = utf8mb4
|
|
|
@ -41,9 +41,7 @@ final class NewPermissionsSystem_20230830_213930 implements IDbMigration {
|
||||||
$conn->execute('
|
$conn->execute('
|
||||||
ALTER TABLE msz_perms
|
ALTER TABLE msz_perms
|
||||||
ADD CONSTRAINT perms_53bit
|
ADD CONSTRAINT perms_53bit
|
||||||
CHECK (perms_allow >= 0 AND perms_deny >= 0 AND perms_allow <= 9007199254740991 AND perms_deny <= 9007199254740991),
|
CHECK (perms_allow >= 0 AND perms_deny >= 0 AND perms_allow <= 9007199254740991 AND perms_deny <= 9007199254740991)
|
||||||
ADD CONSTRAINT perms_only_user_or_role
|
|
||||||
CHECK ((user_id IS NULL AND role_id IS NULL) OR (user_id IS NULL AND role_id IS NOT NULL) OR (user_id IS NOT NULL AND role_id IS NULL))
|
|
||||||
');
|
');
|
||||||
|
|
||||||
$conn->execute('
|
$conn->execute('
|
||||||
|
|
48
database/2024_01_30_233734_create_messages_table.php
Normal file
48
database/2024_01_30_233734_create_messages_table.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
use Index\Data\Migration\IDbMigration;
|
||||||
|
|
||||||
|
final class CreateMessagesTable_20240130_233734 implements IDbMigration {
|
||||||
|
public function migrate(IDbConnection $conn): void {
|
||||||
|
$conn->execute('
|
||||||
|
CREATE TABLE msz_messages (
|
||||||
|
msg_id BINARY(8) NOT NULL,
|
||||||
|
msg_owner_id INT(10) UNSIGNED NOT NULL,
|
||||||
|
msg_author_id INT(10) UNSIGNED NULL DEFAULT NULL,
|
||||||
|
msg_recipient_id INT(10) UNSIGNED NULL DEFAULT NULL,
|
||||||
|
msg_reply_to BINARY(8) NULL DEFAULT NULL,
|
||||||
|
msg_title TINYTEXT NOT NULL COLLATE "utf8mb4_unicode_520_ci",
|
||||||
|
msg_body TEXT NOT NULL COLLATE "utf8mb4_unicode_520_ci",
|
||||||
|
msg_parser TINYINT(3) UNSIGNED NOT NULL,
|
||||||
|
msg_created TIMESTAMP NOT NULL DEFAULT current_timestamp(),
|
||||||
|
msg_sent TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
msg_read TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
msg_deleted TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (msg_id, msg_owner_id),
|
||||||
|
KEY messages_owner_foreign (msg_owner_id),
|
||||||
|
KEY messages_author_foreign (msg_author_id),
|
||||||
|
KEY messages_recipient_foreign (msg_recipient_id),
|
||||||
|
KEY messages_reply_to_index (msg_reply_to),
|
||||||
|
KEY messages_created_index (msg_created),
|
||||||
|
KEY messages_sent_index (msg_sent),
|
||||||
|
KEY messages_read_index (msg_read),
|
||||||
|
KEY messages_deleted_index (msg_deleted),
|
||||||
|
CONSTRAINT messages_owner_foreign
|
||||||
|
FOREIGN KEY (msg_owner_id)
|
||||||
|
REFERENCES msz_users (user_id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
CONSTRAINT messages_author_foreign
|
||||||
|
FOREIGN KEY (msg_author_id)
|
||||||
|
REFERENCES msz_users (user_id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE SET NULL,
|
||||||
|
CONSTRAINT messages_recipient_foreign
|
||||||
|
FOREIGN KEY (msg_recipient_id)
|
||||||
|
REFERENCES msz_users (user_id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE SET NULL
|
||||||
|
) ENGINE=InnoDB COLLATE=utf8mb4_bin;
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,29 @@
|
||||||
# Source Code Repositories
|
# Source Code Repositories
|
||||||
|
|
||||||
- [Misuzu](https://git.flash.moe/flashii/misuzu): Backend of the main website.
|
Below are a number of links to source code repositories related to Flashii.net and its services.
|
||||||
- [Sharp Chat](https://git.flash.moe/flashii/sharp-chat): Chat Server software.
|
|
||||||
- [Backup Manager](https://git.flash.moe/flashii/backup-manager): Program that runs every day at 12:00am UTC to back up any user generated content.
|
## Websites & Services
|
||||||
- [Index](https://git.flash.moe/flash/index): Base library used in almost any component of the website that uses PHP.
|
- [Misuzu](https://patchii.net/flashii/misuzu): Backend of the main website.
|
||||||
- [Uiharu](https://git.flash.moe/flashii/uiharu): Service for looking up URL metadata.
|
- [Sharp Chat](https://patchii.net/flashii/sharp-chat): Chat Server software.
|
||||||
- [Futami](https://git.flash.moe/flashii/futami): Common data shared between the chat clients.
|
- [Futami](https://patchii.net/flashii/futami): Common data shared between the chat clients.
|
||||||
- [AJAX Chat (fork)](https://git.flash.moe/flashii/ajax-chat): Old chat software (2013-2015).
|
- [Mami](https://patchii.net/flashii/mami): Web client for chat.
|
||||||
- [Seria](https://git.flash.moe/flashii/seria): Software used by the torrent tracker.
|
- [Ami](https://patchii.net/flashii/ami): Web client for chat for older browsers.
|
||||||
- [Mince](https://git.flash.moe/flashii/mince): Source code for the Minecraft servers subwebsite.
|
- [EEPROM](https://patchii.net/flashii/eeprom): Service for file uploading.
|
||||||
- [Awaki](https://git.flash.moe/flashii/awaki): Redirect service hosted on fii.moe.
|
- [Uiharu](https://patchii.net/flashii/uiharu): Service for looking up URL metadata.
|
||||||
|
- [Seria](https://patchii.net/flashii/seria): Software used by the downloads tracker.
|
||||||
|
- [Mince](https://patchii.net/flashii/mince): Source code for the Minecraft servers subwebsite.
|
||||||
|
- [Awaki](https://patchii.net/flashii/awaki): Redirect service hosted on fii.moe.
|
||||||
|
|
||||||
|
## Tools & Software
|
||||||
|
- [SoFii](https://patchii.net/flashii/sofii): Launcher for Soldier of Fortune 2
|
||||||
|
- [MCExts](https://patchii.net/flashii/mcexts): Minecraft Client and Server extensions.
|
||||||
|
- [Backup Tools](https://patchii.net/flashii/backup-tools): Scripts that run every day at 12:00am UTC to back up any user generated content.
|
||||||
|
|
||||||
|
## First-Party Libraries
|
||||||
|
- [Index](https://patchii.net/flash/index): Base library used in almost any component of the website that uses PHP.
|
||||||
|
- [Sasae](https://patchii.net/flash/sasae): Extension to the Twig templating library.
|
||||||
|
- [Syokuhou](https://patchii.net/flash/syokuhou): Configuration library.
|
||||||
|
|
||||||
|
## Historical
|
||||||
|
- [AJAX Chat (fork)](https://patchii.net/flashii/ajax-chat): Old chat software (2013-2015). Still kept on life support for the nostalgia.
|
||||||
|
- [Hajime](https://patchii.net/flash/hajime): Cleaned up source of an older version of the website (2014-2015).
|
||||||
|
|
29
misuzu.php
29
misuzu.php
|
@ -3,7 +3,8 @@ namespace Misuzu;
|
||||||
|
|
||||||
use Index\Environment;
|
use Index\Environment;
|
||||||
use Index\Data\DbTools;
|
use Index\Data\DbTools;
|
||||||
use Misuzu\Config\DbConfig;
|
use Syokuhou\DbConfig;
|
||||||
|
use Syokuhou\SharpConfig;
|
||||||
|
|
||||||
define('MSZ_STARTUP', microtime(true));
|
define('MSZ_STARTUP', microtime(true));
|
||||||
define('MSZ_ROOT', __DIR__);
|
define('MSZ_ROOT', __DIR__);
|
||||||
|
@ -19,22 +20,28 @@ define('MSZ_ASSETS', MSZ_ROOT . '/assets');
|
||||||
require_once MSZ_ROOT . '/vendor/autoload.php';
|
require_once MSZ_ROOT . '/vendor/autoload.php';
|
||||||
|
|
||||||
Environment::setDebug(MSZ_DEBUG);
|
Environment::setDebug(MSZ_DEBUG);
|
||||||
mb_internal_encoding('utf-8');
|
mb_internal_encoding('UTF-8');
|
||||||
date_default_timezone_set('utc');
|
date_default_timezone_set('UTC');
|
||||||
|
|
||||||
require_once MSZ_SOURCE . '/url.php';
|
$cfg = SharpConfig::fromFile(MSZ_CONFIG . '/config.cfg');
|
||||||
|
|
||||||
$dbConfig = parse_ini_file(MSZ_CONFIG . '/config.ini', true, INI_SCANNER_TYPED);
|
if($cfg->hasValues('sentry:dsn'))
|
||||||
|
(function($cfg) {
|
||||||
|
\Sentry\init([
|
||||||
|
'dsn' => $cfg->getString('dsn'),
|
||||||
|
'traces_sample_rate' => $cfg->getFloat('tracesRate', 0.2),
|
||||||
|
'profiles_sample_rate' => $cfg->getFloat('profilesRate', 0.2),
|
||||||
|
]);
|
||||||
|
|
||||||
if(empty($dbConfig)) {
|
set_exception_handler(function(\Throwable $ex) {
|
||||||
echo 'Database config is missing.';
|
\Sentry\captureException($ex);
|
||||||
exit;
|
});
|
||||||
}
|
})($cfg->scopeTo('sentry'));
|
||||||
|
|
||||||
$db = DbTools::create($dbConfig['dsn']);
|
$db = DbTools::create($cfg->getString('database:dsn', 'null:'));
|
||||||
$db->execute('SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\';');
|
$db->execute('SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\';');
|
||||||
|
|
||||||
$cfg = new DbConfig($db);
|
$cfg = new DbConfig($db, 'msz_config');
|
||||||
|
|
||||||
Mailer::init($cfg->scopeTo('mail'));
|
Mailer::init($cfg->scopeTo('mail'));
|
||||||
|
|
||||||
|
|
576
package-lock.json
generated
576
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@swc/core": "^1.3.69",
|
"@swc/core": "^1.4.11",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.19",
|
||||||
"cssnano": "^6.0.1",
|
"cssnano": "^6.1.2",
|
||||||
"postcss": "^8.4.26"
|
"postcss": "^8.4.38"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,11 +43,11 @@ header('Content-Type: text/plain; charset=utf-8');
|
||||||
if($_SERVER['REQUEST_METHOD'] !== 'POST')
|
if($_SERVER['REQUEST_METHOD'] !== 'POST')
|
||||||
die('no');
|
die('no');
|
||||||
|
|
||||||
$config = MSZ_ROOT . '/config/github.ini';
|
$config = MSZ_CONFIG . '/github.ini';
|
||||||
if(!is_file($config))
|
if(!is_file($config))
|
||||||
die('config missing');
|
die('config missing');
|
||||||
|
|
||||||
$config = parse_ini_file(MSZ_ROOT . '/config/github.ini', true);
|
$config = parse_ini_file($config, true);
|
||||||
if(empty($config['tokens']['token']))
|
if(empty($config['tokens']['token']))
|
||||||
die('config invalid');
|
die('config invalid');
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Misuzu;
|
|
||||||
|
|
||||||
url_redirect('auth-login');
|
|
|
@ -4,14 +4,17 @@ namespace Misuzu;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Misuzu\Auth\AuthTokenCookie;
|
use Misuzu\Auth\AuthTokenCookie;
|
||||||
|
|
||||||
if($msz->isLoggedIn()) {
|
$urls = $msz->getURLs();
|
||||||
url_redirect('index');
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if($authInfo->isLoggedIn()) {
|
||||||
|
Tools::redirect($urls->format('index'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$authCtx = $msz->getAuthContext();
|
||||||
$sessions = $msz->getSessions();
|
$users = $msz->getUsersContext()->getUsers();
|
||||||
$loginAttempts = $msz->getLoginAttempts();
|
$sessions = $authCtx->getSessions();
|
||||||
|
$loginAttempts = $authCtx->getLoginAttempts();
|
||||||
|
|
||||||
if(!empty($_GET['resolve'])) {
|
if(!empty($_GET['resolve'])) {
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
@ -23,7 +26,7 @@ if(!empty($_GET['resolve'])) {
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'id' => 0,
|
'id' => 0,
|
||||||
'name' => '',
|
'name' => '',
|
||||||
'avatar' => url('user-avatar', ['res' => 200, 'user' => 0]),
|
'avatar' => $urls->format('user-avatar', ['res' => 200, 'user' => 0]),
|
||||||
]);
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -31,7 +34,7 @@ if(!empty($_GET['resolve'])) {
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'id' => (int)$userInfo->getId(),
|
'id' => (int)$userInfo->getId(),
|
||||||
'name' => $userInfo->getName(),
|
'name' => $userInfo->getName(),
|
||||||
'avatar' => url('user-avatar', ['user' => $userInfo->getId(), 'res' => 200]),
|
'avatar' => $urls->format('user-avatar', ['user' => $userInfo->getId(), 'res' => 200]),
|
||||||
]);
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -120,10 +123,8 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if($userInfo->hasTOTPKey()) {
|
if($userInfo->hasTOTPKey()) {
|
||||||
$tfaToken = $msz->getTFASessions()->createToken($userInfo);
|
$tfaToken = $authCtx->getTwoFactorAuthSessions()->createToken($userInfo);
|
||||||
url_redirect('auth-two-factor', [
|
Tools::redirect($urls->format('auth-two-factor', ['token' => $tfaToken]));
|
||||||
'token' => $tfaToken,
|
|
||||||
]);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +137,7 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tokenBuilder = $msz->getAuthInfo()->getTokenInfo()->toBuilder();
|
$tokenBuilder = $authInfo->getTokenInfo()->toBuilder();
|
||||||
$tokenBuilder->setUserId($userInfo);
|
$tokenBuilder->setUserId($userInfo);
|
||||||
$tokenBuilder->setSessionToken($sessionInfo);
|
$tokenBuilder->setSessionToken($sessionInfo);
|
||||||
$tokenBuilder->removeImpersonatedUserId();
|
$tokenBuilder->removeImpersonatedUserId();
|
||||||
|
@ -144,10 +145,10 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
|
||||||
|
|
||||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||||
|
|
||||||
if(!is_local_url($loginRedirect))
|
if(!Tools::isLocalURL($loginRedirect))
|
||||||
$loginRedirect = url('index');
|
$loginRedirect = $urls->format('index');
|
||||||
|
|
||||||
redirect($loginRedirect);
|
Tools::redirect($loginRedirect);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +156,7 @@ $welcomeMode = !empty($_GET['welcome']);
|
||||||
$loginUsername = !empty($_POST['login']['username']) && is_string($_POST['login']['username']) ? $_POST['login']['username'] : (
|
$loginUsername = !empty($_POST['login']['username']) && is_string($_POST['login']['username']) ? $_POST['login']['username'] : (
|
||||||
!empty($_GET['username']) && is_string($_GET['username']) ? $_GET['username'] : ''
|
!empty($_GET['username']) && is_string($_GET['username']) ? $_GET['username'] : ''
|
||||||
);
|
);
|
||||||
$loginRedirect = $welcomeMode ? url('index') : (!empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : null) ?? $_SERVER['HTTP_REFERER'] ?? url('index');
|
$loginRedirect = $welcomeMode ? $urls->format('index') : (!empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : null) ?? $_SERVER['HTTP_REFERER'] ?? $urls->format('index');
|
||||||
$canRegisterAccount = !$siteIsPrivate;
|
$canRegisterAccount = !$siteIsPrivate;
|
||||||
|
|
||||||
Template::render('auth.login', [
|
Template::render('auth.login', [
|
||||||
|
|
|
@ -3,15 +3,17 @@ namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\Auth\AuthTokenCookie;
|
use Misuzu\Auth\AuthTokenCookie;
|
||||||
|
|
||||||
if($msz->isLoggedIn()) {
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if($authInfo->isLoggedIn()) {
|
||||||
if(!CSRF::validateRequest()) {
|
if(!CSRF::validateRequest()) {
|
||||||
Template::render('auth.logout');
|
Template::render('auth.logout');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tokenInfo = $msz->getAuthInfo()->getTokenInfo();
|
$tokenInfo = $authInfo->getTokenInfo();
|
||||||
|
|
||||||
$msz->getSessions()->deleteSessions(sessionTokens: $tokenInfo->getSessionToken());
|
$authCtx = $msz->getAuthContext();
|
||||||
|
$authCtx->getSessions()->deleteSessions(sessionTokens: $tokenInfo->getSessionToken());
|
||||||
|
|
||||||
$tokenBuilder = $tokenInfo->toBuilder();
|
$tokenBuilder = $tokenInfo->toBuilder();
|
||||||
$tokenBuilder->removeUserId();
|
$tokenBuilder->removeUserId();
|
||||||
|
@ -22,4 +24,4 @@ if($msz->isLoggedIn()) {
|
||||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('index');
|
Tools::redirect($msz->getURLs()->format('index'));;
|
||||||
|
|
|
@ -4,14 +4,17 @@ namespace Misuzu;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
if($msz->isLoggedIn()) {
|
$urls = $msz->getURLs();
|
||||||
url_redirect('settings-account');
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if($authInfo->isLoggedIn()) {
|
||||||
|
Tools::redirect($urls->format('settings-account'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$authCtx = $msz->getAuthContext();
|
||||||
$recoveryTokens = $msz->getRecoveryTokens();
|
$users = $msz->getUsersContext()->getUsers();
|
||||||
$loginAttempts = $msz->getLoginAttempts();
|
$recoveryTokens = $authCtx->getRecoveryTokens();
|
||||||
|
$loginAttempts = $authCtx->getLoginAttempts();
|
||||||
|
|
||||||
$reset = !empty($_POST['reset']) && is_array($_POST['reset']) ? $_POST['reset'] : [];
|
$reset = !empty($_POST['reset']) && is_array($_POST['reset']) ? $_POST['reset'] : [];
|
||||||
$forgot = !empty($_POST['forgot']) && is_array($_POST['forgot']) ? $_POST['forgot'] : [];
|
$forgot = !empty($_POST['forgot']) && is_array($_POST['forgot']) ? $_POST['forgot'] : [];
|
||||||
|
@ -23,7 +26,7 @@ if($userId > 0)
|
||||||
try {
|
try {
|
||||||
$userInfo = $users->getUser((string)$userId, 'id');
|
$userInfo = $users->getUser((string)$userId, 'id');
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
url_redirect('auth-forgot');
|
Tools::redirect($urls->format('auth-forgot'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +81,7 @@ while($canResetPassword) {
|
||||||
|
|
||||||
$recoveryTokens->invalidateToken($tokenInfo);
|
$recoveryTokens->invalidateToken($tokenInfo);
|
||||||
|
|
||||||
url_redirect('auth-login', ['redirect' => '/']);
|
Tools::redirect($urls->format('auth-login', ['redirect' => '/']));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +134,7 @@ while($canResetPassword) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('auth-reset', ['user' => $forgotUser->getId()]);
|
Tools::redirect($urls->format('auth-reset', ['user' => $forgotUser->getId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,17 @@ namespace Misuzu;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
if($msz->isLoggedIn()) {
|
$urls = $msz->getURLs();
|
||||||
url_redirect('index');
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if($authInfo->isLoggedIn()) {
|
||||||
|
Tools::redirect($urls->format('index'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$authCtx = $msz->getAuthContext();
|
||||||
$roles = $msz->getRoles();
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$users = $usersCtx->getUsers();
|
||||||
|
$roles = $usersCtx->getRoles();
|
||||||
$config = $msz->getConfig();
|
$config = $msz->getConfig();
|
||||||
|
|
||||||
$register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : [];
|
$register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : [];
|
||||||
|
@ -29,7 +33,7 @@ $countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
|
||||||
// for fast matching
|
// for fast matching
|
||||||
$restricted = '';
|
$restricted = '';
|
||||||
|
|
||||||
$loginAttempts = $msz->getLoginAttempts();
|
$loginAttempts = $authCtx->getLoginAttempts();
|
||||||
$remainingAttempts = $loginAttempts->countRemainingAttempts($ipAddress);
|
$remainingAttempts = $loginAttempts->countRemainingAttempts($ipAddress);
|
||||||
|
|
||||||
while(!$restricted && !empty($register)) {
|
while(!$restricted && !empty($register)) {
|
||||||
|
@ -101,9 +105,12 @@ while(!$restricted && !empty($register)) {
|
||||||
|
|
||||||
$users->addRoles($userInfo, $defaultRoleInfo);
|
$users->addRoles($userInfo, $defaultRoleInfo);
|
||||||
$config->setString('users.newest', $userInfo->getId());
|
$config->setString('users.newest', $userInfo->getId());
|
||||||
$msz->getPerms()->precalculatePermissions($msz->getForum(), [$userInfo->getId()]);
|
$msz->getPerms()->precalculatePermissions(
|
||||||
|
$msz->getForumContext()->getCategories(),
|
||||||
|
[$userInfo->getId()]
|
||||||
|
);
|
||||||
|
|
||||||
url_redirect('auth-login-welcome', ['username' => $userInfo->getName()]);
|
Tools::redirect($urls->format('auth-login-welcome', ['username' => $userInfo->getName()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\Auth\AuthTokenCookie;
|
use Misuzu\Auth\AuthTokenCookie;
|
||||||
|
|
||||||
|
$urls = $msz->getURLs();
|
||||||
if(CSRF::validateRequest()) {
|
if(CSRF::validateRequest()) {
|
||||||
$tokenInfo = $msz->getAuthInfo()->getTokenInfo();
|
$tokenInfo = $msz->getAuthInfo()->getTokenInfo();
|
||||||
|
|
||||||
|
@ -14,9 +15,9 @@ if(CSRF::validateRequest()) {
|
||||||
$tokenInfo = $tokenBuilder->toInfo();
|
$tokenInfo = $tokenBuilder->toInfo();
|
||||||
|
|
||||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||||
url_redirect('manage-user', ['user' => $impUserId]);
|
Tools::redirect($urls->format('manage-user', ['user' => $impUserId]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('index');
|
Tools::redirect($urls->format('index'));
|
||||||
|
|
|
@ -5,15 +5,18 @@ use RuntimeException;
|
||||||
use Misuzu\TOTPGenerator;
|
use Misuzu\TOTPGenerator;
|
||||||
use Misuzu\Auth\AuthTokenCookie;
|
use Misuzu\Auth\AuthTokenCookie;
|
||||||
|
|
||||||
if($msz->isLoggedIn()) {
|
$urls = $msz->getURLs();
|
||||||
url_redirect('index');
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if($authInfo->isLoggedIn()) {
|
||||||
|
Tools::redirect($urls->format('index'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$authCtx = $msz->getAuthContext();
|
||||||
$sessions = $msz->getSessions();
|
$users = $msz->getUsersContext()->getUsers();
|
||||||
$tfaSessions = $msz->getTFASessions();
|
$sessions = $authCtx->getSessions();
|
||||||
$loginAttempts = $msz->getLoginAttempts();
|
$tfaSessions = $authCtx->getTwoFactorAuthSessions();
|
||||||
|
$loginAttempts = $authCtx->getLoginAttempts();
|
||||||
|
|
||||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||||
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
|
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
|
||||||
|
@ -29,7 +32,7 @@ $tokenString = !empty($_GET['token']) && is_string($_GET['token']) ? $_GET['toke
|
||||||
|
|
||||||
$tokenUserId = $tfaSessions->getTokenUserId($tokenString);
|
$tokenUserId = $tfaSessions->getTokenUserId($tokenString);
|
||||||
if(empty($tokenUserId)) {
|
if(empty($tokenUserId)) {
|
||||||
url_redirect('auth-login');
|
Tools::redirect($urls->format('auth-login'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +41,7 @@ $userInfo = $users->getUser($tokenUserId, 'id');
|
||||||
// checking user_totp_key specifically because there's a fringe chance that
|
// checking user_totp_key specifically because there's a fringe chance that
|
||||||
// there's a token present, but totp is actually disabled
|
// there's a token present, but totp is actually disabled
|
||||||
if(!$userInfo->hasTOTPKey()) {
|
if(!$userInfo->hasTOTPKey()) {
|
||||||
url_redirect('auth-login');
|
Tools::redirect($urls->format('auth-login'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +87,7 @@ while(!empty($twofactor)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tokenBuilder = $msz->getAuthInfo()->getTokenInfo()->toBuilder();
|
$tokenBuilder = $authInfo->getTokenInfo()->toBuilder();
|
||||||
$tokenBuilder->setUserId($userInfo);
|
$tokenBuilder->setUserId($userInfo);
|
||||||
$tokenBuilder->setSessionToken($sessionInfo);
|
$tokenBuilder->setSessionToken($sessionInfo);
|
||||||
$tokenBuilder->removeImpersonatedUserId();
|
$tokenBuilder->removeImpersonatedUserId();
|
||||||
|
@ -92,16 +95,16 @@ while(!empty($twofactor)) {
|
||||||
|
|
||||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||||
|
|
||||||
if(!is_local_url($redirect))
|
if(!Tools::isLocalURL($redirect))
|
||||||
$redirect = url('index');
|
$redirect = $urls->format('index');
|
||||||
|
|
||||||
redirect($redirect);
|
Tools::redirect($redirect);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Template::render('auth.twofactor', [
|
Template::render('auth.twofactor', [
|
||||||
'twofactor_notices' => $notices,
|
'twofactor_notices' => $notices,
|
||||||
'twofactor_redirect' => !empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : url('index'),
|
'twofactor_redirect' => !empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : $urls->format('index'),
|
||||||
'twofactor_attempts_remaining' => $remainingAttempts,
|
'twofactor_attempts_remaining' => $remainingAttempts,
|
||||||
'twofactor_token' => $tokenString,
|
'twofactor_token' => $tokenString,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -3,24 +3,26 @@ namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
$redirect = filter_input(INPUT_GET, 'return') ?? $_SERVER['HTTP_REFERER'] ?? url('index');
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$redirect = filter_input(INPUT_GET, 'return') ?? $_SERVER['HTTP_REFERER'] ?? $msz->getURLs()->format('index');
|
||||||
|
|
||||||
if(!is_local_url($redirect))
|
if(!Tools::isLocalURL($redirect))
|
||||||
Template::displayInfo('Possible request forgery detected.', 403);
|
Template::displayInfo('Possible request forgery detected.', 403);
|
||||||
|
|
||||||
if(!CSRF::validateRequest())
|
if(!CSRF::validateRequest())
|
||||||
Template::displayInfo("Couldn't verify this request, please refresh the page and try again.", 403);
|
Template::displayInfo("Couldn't verify this request, please refresh the page and try again.", 403);
|
||||||
|
|
||||||
if(!$msz->isLoggedIn())
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!$authInfo->isLoggedIn())
|
||||||
Template::displayInfo('You must be logged in to manage comments.', 403);
|
Template::displayInfo('You must be logged in to manage comments.', 403);
|
||||||
|
|
||||||
if($msz->hasActiveBan())
|
$currentUserInfo = $authInfo->getUserInfo();
|
||||||
|
|
||||||
|
if($usersCtx->hasActiveBan($currentUserInfo))
|
||||||
Template::displayInfo('You have been banned, check your profile for more information.', 403);
|
Template::displayInfo('You have been banned, check your profile for more information.', 403);
|
||||||
|
|
||||||
$currentUserInfo = $msz->getActiveUser();
|
|
||||||
|
|
||||||
$comments = $msz->getComments();
|
$comments = $msz->getComments();
|
||||||
$perms = $msz->getAuthInfo()->getPerms('global');
|
$perms = $authInfo->getPerms('global');
|
||||||
|
|
||||||
$commentId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
|
$commentId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$commentMode = (string)filter_input(INPUT_GET, 'm');
|
$commentMode = (string)filter_input(INPUT_GET, 'm');
|
||||||
|
@ -65,7 +67,7 @@ switch($commentMode) {
|
||||||
$comments->unpinPost($commentInfo);
|
$comments->unpinPost($commentInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect($redirect . '#comment-' . $commentInfo->getId());
|
Tools::redirect($redirect . '#comment-' . $commentInfo->getId());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'vote':
|
case 'vote':
|
||||||
|
@ -82,7 +84,7 @@ switch($commentMode) {
|
||||||
else
|
else
|
||||||
$comments->removePostVote($commentInfo, $currentUserInfo);
|
$comments->removePostVote($commentInfo, $currentUserInfo);
|
||||||
|
|
||||||
redirect($redirect . '#comment-' . $commentInfo->getId());
|
Tools::redirect($redirect . '#comment-' . $commentInfo->getId());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'delete':
|
case 'delete':
|
||||||
|
@ -115,7 +117,7 @@ switch($commentMode) {
|
||||||
$msz->createAuditLog('COMMENT_ENTRY_DELETE', [$commentInfo->getId()]);
|
$msz->createAuditLog('COMMENT_ENTRY_DELETE', [$commentInfo->getId()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect($redirect);
|
Tools::redirect($redirect);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'restore':
|
case 'restore':
|
||||||
|
@ -133,7 +135,7 @@ switch($commentMode) {
|
||||||
'<username>',
|
'<username>',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
redirect($redirect . '#comment-' . $commentInfo->getId());
|
Tools::redirect($redirect . '#comment-' . $commentInfo->getId());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'create':
|
case 'create':
|
||||||
|
@ -199,7 +201,7 @@ switch($commentMode) {
|
||||||
$commentPin
|
$commentPin
|
||||||
);
|
);
|
||||||
|
|
||||||
redirect($redirect . '#comment-' . $commentInfo->getId());
|
Tools::redirect($redirect . '#comment-' . $commentInfo->getId());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -4,39 +4,43 @@ namespace Misuzu;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
$forum = $msz->getForum();
|
$forumCtx = $msz->getForumContext();
|
||||||
$users = $msz->getUsers();
|
$forumCategories = $forumCtx->getCategories();
|
||||||
|
$forumTopics = $forumCtx->getTopics();
|
||||||
|
$forumPosts = $forumCtx->getPosts();
|
||||||
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
|
||||||
$categoryId = (int)filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT);
|
$categoryId = (int)filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$categoryInfo = $forum->getCategory(categoryId: $categoryId);
|
$categoryInfo = $forumCategories->getCategory(categoryId: $categoryId);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$perms = $msz->getAuthInfo()->getPerms('forum', $categoryInfo);
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
$perms = $authInfo->getPerms('forum', $categoryInfo);
|
||||||
|
|
||||||
$currentUser = $msz->getActiveUser();
|
$currentUser = $authInfo->getUserInfo();
|
||||||
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
||||||
|
|
||||||
if(!$perms->check(Perm::F_CATEGORY_VIEW))
|
if(!$perms->check(Perm::F_CATEGORY_VIEW))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
if($msz->hasActiveBan())
|
if($usersCtx->hasActiveBan($currentUser))
|
||||||
$perms = $perms->apply(fn($calc) => $calc & (Perm::F_CATEGORY_LIST | Perm::F_CATEGORY_VIEW));
|
$perms = $perms->apply(fn($calc) => $calc & (Perm::F_CATEGORY_LIST | Perm::F_CATEGORY_VIEW));
|
||||||
|
|
||||||
if($categoryInfo->isLink()) {
|
if($categoryInfo->isLink()) {
|
||||||
if($categoryInfo->hasLinkTarget()) {
|
if($categoryInfo->hasLinkTarget()) {
|
||||||
$forum->incrementCategoryClicks($categoryInfo);
|
$forumCategories->incrementCategoryClicks($categoryInfo);
|
||||||
redirect($categoryInfo->getLinkTarget());
|
Tools::redirect($categoryInfo->getLinkTarget());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$forumPagination = new Pagination($forum->countTopics(
|
$forumPagination = new Pagination($forumTopics->countTopics(
|
||||||
categoryInfo: $categoryInfo,
|
categoryInfo: $categoryInfo,
|
||||||
global: true,
|
global: true,
|
||||||
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false
|
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false
|
||||||
|
@ -45,16 +49,14 @@ $forumPagination = new Pagination($forum->countTopics(
|
||||||
if(!$forumPagination->hasValidOffset())
|
if(!$forumPagination->hasValidOffset())
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
|
|
||||||
$userInfos = [];
|
|
||||||
$userColours = [];
|
|
||||||
$children = [];
|
$children = [];
|
||||||
$topics = [];
|
$topics = [];
|
||||||
|
|
||||||
if($categoryInfo->mayHaveChildren()) {
|
if($categoryInfo->mayHaveChildren()) {
|
||||||
$children = $forum->getCategoryChildren($categoryInfo, hidden: false, asTree: true);
|
$children = $forumCategories->getCategoryChildren($categoryInfo, hidden: false, asTree: true);
|
||||||
|
|
||||||
foreach($children as $childId => $child) {
|
foreach($children as $childId => $child) {
|
||||||
$childPerms = $msz->getAuthInfo()->getPerms('forum', $child->info);
|
$childPerms = $authInfo->getPerms('forum', $child->info);
|
||||||
if(!$childPerms->check(Perm::F_CATEGORY_LIST)) {
|
if(!$childPerms->check(Perm::F_CATEGORY_LIST)) {
|
||||||
unset($category->children[$childId]);
|
unset($category->children[$childId]);
|
||||||
continue;
|
continue;
|
||||||
|
@ -64,7 +66,7 @@ if($categoryInfo->mayHaveChildren()) {
|
||||||
|
|
||||||
if($child->info->mayHaveChildren()) {
|
if($child->info->mayHaveChildren()) {
|
||||||
foreach($child->children as $grandChildId => $grandChild) {
|
foreach($child->children as $grandChildId => $grandChild) {
|
||||||
$grandChildPerms = $msz->getAuthInfo()->getPerms('forum', $grandChild->info);
|
$grandChildPerms = $authInfo->getPerms('forum', $grandChild->info);
|
||||||
if(!$grandChildPerms->check(Perm::F_CATEGORY_LIST)) {
|
if(!$grandChildPerms->check(Perm::F_CATEGORY_LIST)) {
|
||||||
unset($child->children[$grandChildId]);
|
unset($child->children[$grandChildId]);
|
||||||
continue;
|
continue;
|
||||||
|
@ -75,12 +77,12 @@ if($categoryInfo->mayHaveChildren()) {
|
||||||
if($grandChild->info->mayHaveTopics()) {
|
if($grandChild->info->mayHaveTopics()) {
|
||||||
$catIds = [$grandChild->info->getId()];
|
$catIds = [$grandChild->info->getId()];
|
||||||
foreach($grandChild->childIds as $greatGrandChildId) {
|
foreach($grandChild->childIds as $greatGrandChildId) {
|
||||||
$greatGrandChildPerms = $msz->getAuthInfo()->getPerms('forum', $greatGrandChildId);
|
$greatGrandChildPerms = $authInfo->getPerms('forum', $greatGrandChildId);
|
||||||
if(!$greatGrandChildPerms->check(Perm::F_CATEGORY_LIST))
|
if(!$greatGrandChildPerms->check(Perm::F_CATEGORY_LIST))
|
||||||
$catIds[] = $greatGrandChildId;
|
$catIds[] = $greatGrandChildId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$grandChildUnread = $forum->checkCategoryUnread($catIds, $currentUser);
|
$grandChildUnread = $forumCategories->checkCategoryUnread($catIds, $currentUser);
|
||||||
if($grandChildUnread)
|
if($grandChildUnread)
|
||||||
$childUnread = true;
|
$childUnread = true;
|
||||||
}
|
}
|
||||||
|
@ -93,13 +95,13 @@ if($categoryInfo->mayHaveChildren()) {
|
||||||
if($child->info->mayHaveChildren() || $child->info->mayHaveTopics()) {
|
if($child->info->mayHaveChildren() || $child->info->mayHaveTopics()) {
|
||||||
$catIds = [$child->info->getId()];
|
$catIds = [$child->info->getId()];
|
||||||
foreach($child->childIds as $grandChildId) {
|
foreach($child->childIds as $grandChildId) {
|
||||||
$grandChildPerms = $msz->getAuthInfo()->getPerms('forum', $grandChildId);
|
$grandChildPerms = $authInfo->getPerms('forum', $grandChildId);
|
||||||
if($grandChildPerms->check(Perm::F_CATEGORY_LIST))
|
if($grandChildPerms->check(Perm::F_CATEGORY_LIST))
|
||||||
$catIds[] = $grandChildId;
|
$catIds[] = $grandChildId;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$lastPostInfo = $forum->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
|
$lastPostInfo = $forumPosts->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
$lastPostInfo = null;
|
$lastPostInfo = null;
|
||||||
}
|
}
|
||||||
|
@ -107,24 +109,17 @@ if($categoryInfo->mayHaveChildren()) {
|
||||||
if($lastPostInfo !== null) {
|
if($lastPostInfo !== null) {
|
||||||
$child->lastPost = new stdClass;
|
$child->lastPost = new stdClass;
|
||||||
$child->lastPost->info = $lastPostInfo;
|
$child->lastPost->info = $lastPostInfo;
|
||||||
$child->lastPost->topicInfo = $forum->getTopic(postInfo: $lastPostInfo);
|
$child->lastPost->topicInfo = $forumTopics->getTopic(postInfo: $lastPostInfo);
|
||||||
|
|
||||||
if($lastPostInfo->hasUserId()) {
|
if($lastPostInfo->hasUserId()) {
|
||||||
$lastPostUserId = $lastPostInfo->getUserId();
|
$child->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId());
|
||||||
if(!array_key_exists($lastPostUserId, $userInfos)) {
|
$child->lastPost->colour = $usersCtx->getUserColour($child->lastPost->user);
|
||||||
$userInfo = $users->getUser($lastPostUserId, 'id');
|
|
||||||
$userInfos[$lastPostUserId] = $userInfo;
|
|
||||||
$userColours[$lastPostUserId] = $users->getUserColour($userInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$child->lastPost->user = $userInfos[$lastPostUserId];
|
|
||||||
$child->lastPost->colour = $userColours[$lastPostUserId];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($child->info->mayHaveTopics() && !$childUnread)
|
if($child->info->mayHaveTopics() && !$childUnread)
|
||||||
$childUnread = $forum->checkCategoryUnread($child->info, $currentUser);
|
$childUnread = $forumCategories->checkCategoryUnread($child->info, $currentUser);
|
||||||
|
|
||||||
$child->perms = $childPerms;
|
$child->perms = $childPerms;
|
||||||
$child->unread = $childUnread;
|
$child->unread = $childUnread;
|
||||||
|
@ -132,7 +127,7 @@ if($categoryInfo->mayHaveChildren()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if($categoryInfo->mayHaveTopics()) {
|
if($categoryInfo->mayHaveTopics()) {
|
||||||
$topicInfos = $forum->getTopics(
|
$topicInfos = $forumTopics->getTopics(
|
||||||
categoryInfo: $categoryInfo,
|
categoryInfo: $categoryInfo,
|
||||||
global: true,
|
global: true,
|
||||||
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false,
|
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false,
|
||||||
|
@ -142,41 +137,29 @@ if($categoryInfo->mayHaveTopics()) {
|
||||||
foreach($topicInfos as $topicInfo) {
|
foreach($topicInfos as $topicInfo) {
|
||||||
$topics[] = $topic = new stdClass;
|
$topics[] = $topic = new stdClass;
|
||||||
$topic->info = $topicInfo;
|
$topic->info = $topicInfo;
|
||||||
$topic->unread = $forum->checkTopicUnread($topicInfo, $currentUser);
|
$topic->unread = $forumTopics->checkTopicUnread($topicInfo, $currentUser);
|
||||||
$topic->participated = $forum->checkTopicParticipated($topicInfo, $currentUser);
|
$topic->participated = $forumTopics->checkTopicParticipated($topicInfo, $currentUser);
|
||||||
$topic->lastPost = new stdClass;
|
|
||||||
|
|
||||||
if($topicInfo->hasUserId()) {
|
if($topicInfo->hasUserId()) {
|
||||||
$lastTopicUserId = $topicInfo->getUserId();
|
$topic->user = $usersCtx->getUserInfo($topicInfo->getUserId());
|
||||||
if(!array_key_exists($lastTopicUserId, $userInfos)) {
|
$topic->colour = $usersCtx->getUserColour($topic->user);
|
||||||
$userInfo = $users->getUser($lastTopicUserId, 'id');
|
|
||||||
$userInfos[$lastTopicUserId] = $userInfo;
|
|
||||||
$userColours[$lastTopicUserId] = $users->getUserColour($userInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$topic->user = $userInfos[$lastTopicUserId];
|
|
||||||
$topic->colour = $userColours[$lastTopicUserId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$topic->lastPost->info = $lastPostInfo = $forum->getPost(
|
$topic->lastPost = new stdClass;
|
||||||
|
$topic->lastPost->info = $lastPostInfo = $forumPosts->getPost(
|
||||||
topicInfo: $topicInfo,
|
topicInfo: $topicInfo,
|
||||||
getLast: true,
|
getLast: true,
|
||||||
deleted: $topicInfo->isDeleted() ? null : false,
|
deleted: $topicInfo->isDeleted() ? null : false,
|
||||||
);
|
);
|
||||||
|
|
||||||
if($lastPostInfo->hasUserId()) {
|
if($lastPostInfo->hasUserId()) {
|
||||||
$lastPostUserId = $lastPostInfo->getUserId();
|
$topic->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId());
|
||||||
if(!array_key_exists($lastPostUserId, $userInfos)) {
|
$topic->lastPost->colour = $usersCtx->getUserColour($topic->lastPost->user);
|
||||||
$userInfo = $users->getUser($lastPostUserId, 'id');
|
|
||||||
$userInfos[$lastPostUserId] = $userInfo;
|
|
||||||
$userColours[$lastPostUserId] = $users->getUserColour($userInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$topic->lastPost->user = $userInfos[$lastPostUserId];
|
|
||||||
$topic->lastPost->colour = $userColours[$lastPostUserId];
|
|
||||||
}
|
}
|
||||||
} catch(RuntimeException $ex) {}
|
} catch(RuntimeException $ex) {
|
||||||
|
$topic->lastPost = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,8 +168,8 @@ $perms = $perms->checkMany([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Template::render('forum.forum', [
|
Template::render('forum.forum', [
|
||||||
'forum_breadcrumbs' => $forum->getCategoryAncestry($categoryInfo),
|
'forum_breadcrumbs' => iterator_to_array($forumCategories->getCategoryAncestry($categoryInfo)),
|
||||||
'global_accent_colour' => $forum->getCategoryColour($categoryInfo),
|
'global_accent_colour' => $forumCategories->getCategoryColour($categoryInfo),
|
||||||
'forum_info' => $categoryInfo,
|
'forum_info' => $categoryInfo,
|
||||||
'forum_children' => $children,
|
'forum_children' => $children,
|
||||||
'forum_topics' => $topics,
|
'forum_topics' => $topics,
|
||||||
|
|
|
@ -4,38 +4,42 @@ namespace Misuzu;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
$forum = $msz->getForum();
|
$forumCtx = $msz->getForumContext();
|
||||||
$users = $msz->getUsers();
|
$forumCategories = $forumCtx->getCategories();
|
||||||
|
$forumTopics = $forumCtx->getTopics();
|
||||||
|
$forumPosts = $forumCtx->getPosts();
|
||||||
|
$usersCtx = $msz->getUsersContext();
|
||||||
$mode = (string)filter_input(INPUT_GET, 'm');
|
$mode = (string)filter_input(INPUT_GET, 'm');
|
||||||
|
|
||||||
$currentUser = $msz->getActiveUser();
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
$currentUser = $authInfo->getUserInfo();
|
||||||
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
||||||
|
|
||||||
if($mode === 'mark') {
|
if($mode === 'mark') {
|
||||||
if(!$msz->isLoggedIn())
|
if(!$authInfo->isLoggedIn())
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$categoryId = filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT);
|
$categoryId = filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$categoryInfos = $categoryId === null
|
$categoryInfos = $categoryId === null
|
||||||
? $forum->getCategories()
|
? $forumCategories->getCategories()
|
||||||
: $forum->getCategoryChildren(parentInfo: $categoryId, includeSelf: true);
|
: $forumCategories->getCategoryChildren(parentInfo: $categoryId, includeSelf: true);
|
||||||
|
|
||||||
foreach($categoryInfos as $categoryInfo) {
|
foreach($categoryInfos as $categoryInfo) {
|
||||||
$perms = $msz->getAuthInfo()->getPerms('forum', $categoryInfo);
|
$perms = $authInfo->getPerms('forum', $categoryInfo);
|
||||||
if($perms->check(Perm::F_CATEGORY_LIST))
|
if($perms->check(Perm::F_CATEGORY_LIST))
|
||||||
$forum->updateUserReadCategory($userInfo, $categoryInfo);
|
$forumCategories->updateUserReadCategory($userInfo, $categoryInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]);
|
Tools::redirect($msz->getURLs()->format($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Template::render('confirm', [
|
Template::render('confirm', [
|
||||||
'title' => 'Mark forum as read',
|
'title' => 'Mark forum as read',
|
||||||
'message' => 'Are you sure you want to mark ' . ($categoryId < 1 ? 'the entire' : 'this') . ' forum as read?',
|
'message' => 'Are you sure you want to mark ' . ($categoryId < 1 ? 'the entire' : 'this') . ' forum as read?',
|
||||||
'return' => url($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]),
|
'return' => $msz->getURLs()->format($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]),
|
||||||
'params' => [
|
'params' => [
|
||||||
'forum' => $categoryId,
|
'forum' => $categoryId,
|
||||||
]
|
]
|
||||||
|
@ -46,12 +50,10 @@ if($mode === 'mark') {
|
||||||
if($mode !== '')
|
if($mode !== '')
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
|
|
||||||
$userInfos = [];
|
$categories = $forumCategories->getCategories(hidden: false, asTree: true);
|
||||||
$userColours = [];
|
|
||||||
$categories = $forum->getCategories(hidden: false, asTree: true);
|
|
||||||
|
|
||||||
foreach($categories as $categoryId => $category) {
|
foreach($categories as $categoryId => $category) {
|
||||||
$perms = $msz->getAuthInfo()->getPerms('forum', $category->info);
|
$perms = $authInfo->getPerms('forum', $category->info);
|
||||||
if(!$perms->check(Perm::F_CATEGORY_LIST)) {
|
if(!$perms->check(Perm::F_CATEGORY_LIST)) {
|
||||||
unset($categories[$categoryId]);
|
unset($categories[$categoryId]);
|
||||||
continue;
|
continue;
|
||||||
|
@ -61,7 +63,7 @@ foreach($categories as $categoryId => $category) {
|
||||||
|
|
||||||
if($category->info->mayHaveChildren())
|
if($category->info->mayHaveChildren())
|
||||||
foreach($category->children as $childId => $child) {
|
foreach($category->children as $childId => $child) {
|
||||||
$childPerms = $msz->getAuthInfo()->getPerms('forum', $child->info);
|
$childPerms = $authInfo->getPerms('forum', $child->info);
|
||||||
if(!$childPerms->check(Perm::F_CATEGORY_LIST)) {
|
if(!$childPerms->check(Perm::F_CATEGORY_LIST)) {
|
||||||
unset($category->children[$childId]);
|
unset($category->children[$childId]);
|
||||||
continue;
|
continue;
|
||||||
|
@ -72,7 +74,7 @@ foreach($categories as $categoryId => $category) {
|
||||||
if($category->info->isListing()) {
|
if($category->info->isListing()) {
|
||||||
if($child->info->mayHaveChildren()) {
|
if($child->info->mayHaveChildren()) {
|
||||||
foreach($child->children as $grandChildId => $grandChild) {
|
foreach($child->children as $grandChildId => $grandChild) {
|
||||||
$grandChildPerms = $msz->getAuthInfo()->getPerms('forum', $grandChild->info);
|
$grandChildPerms = $authInfo->getPerms('forum', $grandChild->info);
|
||||||
if(!$grandChildPerms->check(Perm::F_CATEGORY_LIST)) {
|
if(!$grandChildPerms->check(Perm::F_CATEGORY_LIST)) {
|
||||||
unset($child->children[$grandChildId]);
|
unset($child->children[$grandChildId]);
|
||||||
continue;
|
continue;
|
||||||
|
@ -83,12 +85,12 @@ foreach($categories as $categoryId => $category) {
|
||||||
if($grandChild->info->mayHaveTopics()) {
|
if($grandChild->info->mayHaveTopics()) {
|
||||||
$catIds = [$grandChild->info->getId()];
|
$catIds = [$grandChild->info->getId()];
|
||||||
foreach($grandChild->childIds as $greatGrandChildId) {
|
foreach($grandChild->childIds as $greatGrandChildId) {
|
||||||
$greatGrandChildPerms = $msz->getAuthInfo()->getPerms('forum', $greatGrandChildId);
|
$greatGrandChildPerms = $authInfo->getPerms('forum', $greatGrandChildId);
|
||||||
if($greatGrandChildPerms->check(Perm::F_CATEGORY_LIST))
|
if($greatGrandChildPerms->check(Perm::F_CATEGORY_LIST))
|
||||||
$catIds[] = $greatGrandChildId;
|
$catIds[] = $greatGrandChildId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$grandChildUnread = $forum->checkCategoryUnread($catIds, $currentUser);
|
$grandChildUnread = $forumCategories->checkCategoryUnread($catIds, $currentUser);
|
||||||
if($grandChildUnread)
|
if($grandChildUnread)
|
||||||
$childUnread = true;
|
$childUnread = true;
|
||||||
}
|
}
|
||||||
|
@ -101,13 +103,13 @@ foreach($categories as $categoryId => $category) {
|
||||||
if($child->info->mayHaveChildren() || $child->info->mayHaveTopics()) {
|
if($child->info->mayHaveChildren() || $child->info->mayHaveTopics()) {
|
||||||
$catIds = [$child->info->getId()];
|
$catIds = [$child->info->getId()];
|
||||||
foreach($child->childIds as $grandChildId) {
|
foreach($child->childIds as $grandChildId) {
|
||||||
$grandChildPerms = $msz->getAuthInfo()->getPerms('forum', $grandChildId);
|
$grandChildPerms = $authInfo->getPerms('forum', $grandChildId);
|
||||||
if($grandChildPerms->check(Perm::F_CATEGORY_LIST))
|
if($grandChildPerms->check(Perm::F_CATEGORY_LIST))
|
||||||
$catIds[] = $grandChildId;
|
$catIds[] = $grandChildId;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$lastPostInfo = $forum->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
|
$lastPostInfo = $forumPosts->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
$lastPostInfo = null;
|
$lastPostInfo = null;
|
||||||
}
|
}
|
||||||
|
@ -115,25 +117,18 @@ foreach($categories as $categoryId => $category) {
|
||||||
if($lastPostInfo !== null) {
|
if($lastPostInfo !== null) {
|
||||||
$child->lastPost = new stdClass;
|
$child->lastPost = new stdClass;
|
||||||
$child->lastPost->info = $lastPostInfo;
|
$child->lastPost->info = $lastPostInfo;
|
||||||
$child->lastPost->topicInfo = $forum->getTopic(postInfo: $lastPostInfo);
|
$child->lastPost->topicInfo = $forumTopics->getTopic(postInfo: $lastPostInfo);
|
||||||
|
|
||||||
if($lastPostInfo->hasUserId()) {
|
if($lastPostInfo->hasUserId()) {
|
||||||
$lastPostUserId = $lastPostInfo->getUserId();
|
$child->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId());
|
||||||
if(!array_key_exists($lastPostUserId, $userInfos)) {
|
$child->lastPost->colour = $usersCtx->getUserColour($child->lastPost->user);
|
||||||
$userInfo = $users->getUser($lastPostUserId, 'id');
|
|
||||||
$userInfos[$lastPostUserId] = $userInfo;
|
|
||||||
$userColours[$lastPostUserId] = $users->getUserColour($userInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$child->lastPost->user = $userInfos[$lastPostUserId];
|
|
||||||
$child->lastPost->colour = $userColours[$lastPostUserId];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($child->info->mayHaveTopics() && !$childUnread) {
|
if($child->info->mayHaveTopics() && !$childUnread) {
|
||||||
$childUnread = $forum->checkCategoryUnread($child->info, $currentUser);
|
$childUnread = $forumCategories->checkCategoryUnread($child->info, $currentUser);
|
||||||
if($childUnread)
|
if($childUnread)
|
||||||
$unread = true;
|
$unread = true;
|
||||||
}
|
}
|
||||||
|
@ -143,7 +138,7 @@ foreach($categories as $categoryId => $category) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if($category->info->mayHaveTopics() && !$unread)
|
if($category->info->mayHaveTopics() && !$unread)
|
||||||
$unread = $forum->checkCategoryUnread($category->info, $currentUser);
|
$unread = $forumCategories->checkCategoryUnread($category->info, $currentUser);
|
||||||
|
|
||||||
if(!$category->info->isListing()) {
|
if(!$category->info->isListing()) {
|
||||||
if(!array_key_exists('0', $categories)) {
|
if(!array_key_exists('0', $categories)) {
|
||||||
|
@ -161,13 +156,13 @@ foreach($categories as $categoryId => $category) {
|
||||||
if($category->info->mayHaveChildren() || $category->info->mayHaveTopics()) {
|
if($category->info->mayHaveChildren() || $category->info->mayHaveTopics()) {
|
||||||
$catIds = [$category->info->getId()];
|
$catIds = [$category->info->getId()];
|
||||||
foreach($category->childIds as $childId) {
|
foreach($category->childIds as $childId) {
|
||||||
$childPerms = $msz->getAuthInfo()->getPerms('forum', $childId);
|
$childPerms = $authInfo->getPerms('forum', $childId);
|
||||||
if($childPerms->check(Perm::F_CATEGORY_LIST))
|
if($childPerms->check(Perm::F_CATEGORY_LIST))
|
||||||
$catIds[] = $childId;
|
$catIds[] = $childId;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$lastPostInfo = $forum->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
|
$lastPostInfo = $forumPosts->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
$lastPostInfo = null;
|
$lastPostInfo = null;
|
||||||
}
|
}
|
||||||
|
@ -175,18 +170,11 @@ foreach($categories as $categoryId => $category) {
|
||||||
if($lastPostInfo !== null) {
|
if($lastPostInfo !== null) {
|
||||||
$category->lastPost = new stdClass;
|
$category->lastPost = new stdClass;
|
||||||
$category->lastPost->info = $lastPostInfo;
|
$category->lastPost->info = $lastPostInfo;
|
||||||
$category->lastPost->topicInfo = $forum->getTopic(postInfo: $lastPostInfo);
|
$category->lastPost->topicInfo = $forumTopics->getTopic(postInfo: $lastPostInfo);
|
||||||
|
|
||||||
if($lastPostInfo->hasUserId()) {
|
if($lastPostInfo->hasUserId()) {
|
||||||
$lastPostUserId = $lastPostInfo->getUserId();
|
$category->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId());
|
||||||
if(!array_key_exists($lastPostUserId, $userInfos)) {
|
$category->lastPost->colour = $usersCtx->getUserColour($category->lastPost->user);
|
||||||
$userInfo = $users->getUser($lastPostInfo->getUserId(), 'id');
|
|
||||||
$userInfos[$lastPostUserId] = $userInfo;
|
|
||||||
$userColours[$lastPostUserId] = $users->getUserColour($userInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$category->lastPost->user = $userInfos[$lastPostUserId];
|
|
||||||
$category->lastPost->colour = $userColours[$lastPostUserId];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ use RuntimeException;
|
||||||
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_FORUM_LEADERBOARD_VIEW))
|
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_FORUM_LEADERBOARD_VIEW))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$forum = $msz->getForum();
|
$forumCtx = $msz->getForumContext();
|
||||||
$users = $msz->getUsers();
|
$usersCtx = $msz->getUsersContext();
|
||||||
$config = $cfg->getValues([
|
$config = $cfg->getValues([
|
||||||
['forum_leader.first_year:i', 2018],
|
['forum_leader.first_year:i', 2018],
|
||||||
['forum_leader.first_month:i', 12],
|
['forum_leader.first_month:i', 12],
|
||||||
|
@ -61,15 +61,12 @@ for($i = $currentYear, $j = $currentMonth;;) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$rankings = $forum->generatePostRankings($year, $month, $unrankedForums, $unrankedTopics);
|
$rankings = $forumCtx->getPosts()->generatePostRankings($year, $month, $unrankedForums, $unrankedTopics);
|
||||||
foreach($rankings as $ranking) {
|
foreach($rankings as $ranking) {
|
||||||
$ranking->user = $ranking->colour = null;
|
$ranking->user = $ranking->colour = null;
|
||||||
|
|
||||||
if($ranking->userId !== '')
|
if($ranking->userId !== '')
|
||||||
try {
|
$ranking->user = $usersCtx->getUserInfo($ranking->userId);
|
||||||
$ranking->user = $users->getUser($ranking->userId);
|
|
||||||
$ranking->colour = $users->getUserColour($ranking->user);
|
|
||||||
} catch(RuntimeException $ex) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = 'All Time';
|
$name = 'All Time';
|
||||||
|
@ -96,7 +93,9 @@ MD;
|
||||||
$totalPostsCount += $ranking->postsCount;
|
$totalPostsCount += $ranking->postsCount;
|
||||||
$markdown .= sprintf("| %s | [%s](%s%s) | %s |\r\n", $ranking->position,
|
$markdown .= sprintf("| %s | [%s](%s%s) | %s |\r\n", $ranking->position,
|
||||||
$ranking->user?->getName() ?? 'Deleted User',
|
$ranking->user?->getName() ?? 'Deleted User',
|
||||||
url_prefix(false), url('user-profile', ['user' => $ranking->userId]), number_format($ranking->postsCount));
|
$msz->getSiteInfo()->getURL(),
|
||||||
|
$msz->getURLs()->format('user-profile', ['user' => $ranking->userId]),
|
||||||
|
number_format($ranking->postsCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
$markdown .= sprintf("\r\nIn total %s posts were made!\r\n", number_format($totalPostsCount));
|
$markdown .= sprintf("\r\nIn total %s posts were made!\r\n", number_format($totalPostsCount));
|
||||||
|
|
|
@ -3,7 +3,10 @@ namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
$forum = $msz->getForum();
|
$urls = $msz->getURLs();
|
||||||
|
$forumCtx = $msz->getForumContext();
|
||||||
|
$forumPosts = $forumCtx->getPosts();
|
||||||
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
|
||||||
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
|
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
|
||||||
$postMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
$postMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
||||||
|
@ -11,22 +14,23 @@ $submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) &
|
||||||
|
|
||||||
$postRequestVerified = CSRF::validateRequest();
|
$postRequestVerified = CSRF::validateRequest();
|
||||||
|
|
||||||
if(!empty($postMode) && !$msz->isLoggedIn())
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!empty($postMode) && !$authInfo->isLoggedIn())
|
||||||
Template::displayInfo('You must be logged in to manage posts.', 401);
|
Template::displayInfo('You must be logged in to manage posts.', 401);
|
||||||
|
|
||||||
$currentUser = $msz->getActiveUser();
|
$currentUser = $authInfo->getUserInfo();
|
||||||
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
||||||
|
|
||||||
if($postMode !== '' && $msz->hasActiveBan())
|
if($postMode !== '' && $usersCtx->hasActiveBan($currentUser))
|
||||||
Template::displayInfo('You have been banned, check your profile for more information.', 403);
|
Template::displayInfo('You have been banned, check your profile for more information.', 403);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$postInfo = $forum->getPost(postId: $postId);
|
$postInfo = $forumPosts->getPost(postId: $postId);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$perms = $msz->getAuthInfo()->getPerms('forum', $postInfo->getCategoryId());
|
$perms = $authInfo->getPerms('forum', $postInfo->getCategoryId());
|
||||||
|
|
||||||
if(!$perms->check(Perm::F_CATEGORY_VIEW))
|
if(!$perms->check(Perm::F_CATEGORY_VIEW))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
@ -54,15 +58,12 @@ switch($postMode) {
|
||||||
Template::displayInfo('This post has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403);
|
Template::displayInfo('This post has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$originalPostInfo = $forum->getPost(topicInfo: $postInfo->getTopicId());
|
$originalPostInfo = $forumPosts->getPost(topicInfo: $postInfo->getTopicId());
|
||||||
if($originalPostInfo->getId() === $postInfo->getId())
|
if($originalPostInfo->getId() === $postInfo->getId())
|
||||||
Template::displayInfo('This is the opening post of the topic it belongs to, it may not be deleted without deleting the entire topic as well.', 403);
|
Template::displayInfo('This is the opening post of the topic it belongs to, it may not be deleted without deleting the entire topic as well.', 403);
|
||||||
|
|
||||||
if($postRequestVerified && !$submissionConfirmed) {
|
if($postRequestVerified && !$submissionConfirmed) {
|
||||||
url_redirect('forum-post', [
|
Tools::redirect($urls->format('forum-post', ['post' => $postInfo->getId()]));
|
||||||
'post' => $postInfo->getId(),
|
|
||||||
'post_fragment' => 'p' . $postInfo->getId(),
|
|
||||||
]);
|
|
||||||
break;
|
break;
|
||||||
} elseif(!$postRequestVerified) {
|
} elseif(!$postRequestVerified) {
|
||||||
Template::render('forum.confirm', [
|
Template::render('forum.confirm', [
|
||||||
|
@ -77,10 +78,10 @@ switch($postMode) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$forum->deletePost($postInfo);
|
$forumPosts->deletePost($postInfo);
|
||||||
$msz->createAuditLog('FORUM_POST_DELETE', [$postInfo->getId()]);
|
$msz->createAuditLog('FORUM_POST_DELETE', [$postInfo->getId()]);
|
||||||
|
|
||||||
url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]);
|
Tools::redirect($urls->format('forum-topic', ['topic' => $postInfo->getTopicId()]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'nuke':
|
case 'nuke':
|
||||||
|
@ -88,10 +89,7 @@ switch($postMode) {
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
if($postRequestVerified && !$submissionConfirmed) {
|
if($postRequestVerified && !$submissionConfirmed) {
|
||||||
url_redirect('forum-post', [
|
Tools::redirect($urls->format('forum-post', ['post' => $postInfo->getId()]));
|
||||||
'post' => $postInfo->getId(),
|
|
||||||
'post_fragment' => 'p' . $postInfo->getId(),
|
|
||||||
]);
|
|
||||||
break;
|
break;
|
||||||
} elseif(!$postRequestVerified) {
|
} elseif(!$postRequestVerified) {
|
||||||
Template::render('forum.confirm', [
|
Template::render('forum.confirm', [
|
||||||
|
@ -106,10 +104,10 @@ switch($postMode) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$forum->nukePost($postInfo->getId());
|
$forumPosts->nukePost($postInfo->getId());
|
||||||
$msz->createAuditLog('FORUM_POST_NUKE', [$postInfo->getId()]);
|
$msz->createAuditLog('FORUM_POST_NUKE', [$postInfo->getId()]);
|
||||||
|
|
||||||
url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]);
|
Tools::redirect($urls->format('forum-topic', ['topic' => $postInfo->getTopicId()]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'restore':
|
case 'restore':
|
||||||
|
@ -117,10 +115,7 @@ switch($postMode) {
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
if($postRequestVerified && !$submissionConfirmed) {
|
if($postRequestVerified && !$submissionConfirmed) {
|
||||||
url_redirect('forum-post', [
|
Tools::redirect($urls->format('forum-post', ['post' => $postInfo->getId()]));
|
||||||
'post' => $postInfo->getId(),
|
|
||||||
'post_fragment' => 'p' . $postInfo->getId(),
|
|
||||||
]);
|
|
||||||
break;
|
break;
|
||||||
} elseif(!$postRequestVerified) {
|
} elseif(!$postRequestVerified) {
|
||||||
Template::render('forum.confirm', [
|
Template::render('forum.confirm', [
|
||||||
|
@ -135,16 +130,13 @@ switch($postMode) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$forum->restorePost($postInfo->getId());
|
$forumPosts->restorePost($postInfo->getId());
|
||||||
$msz->createAuditLog('FORUM_POST_RESTORE', [$postInfo->getId()]);
|
$msz->createAuditLog('FORUM_POST_RESTORE', [$postInfo->getId()]);
|
||||||
|
|
||||||
url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]);
|
Tools::redirect($urls->format('forum-topic', ['topic' => $postInfo->getTopicId()]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // function as an alt for topic.php?p= by default
|
default: // function as an alt for topic.php?p= by default
|
||||||
url_redirect('forum-post', [
|
Tools::redirect($urls->format('forum-post', ['post' => $postInfo->getId()]));
|
||||||
'post' => $postInfo->getId(),
|
|
||||||
'post_fragment' => 'p' . $postInfo->getId(),
|
|
||||||
]);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,21 +7,21 @@ use Index\DateTime;
|
||||||
use Misuzu\Forum\ForumTopicInfo;
|
use Misuzu\Forum\ForumTopicInfo;
|
||||||
use Misuzu\Parsers\Parser;
|
use Misuzu\Parsers\Parser;
|
||||||
|
|
||||||
if(!$msz->isLoggedIn())
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!$authInfo->isLoggedIn())
|
||||||
Template::throwError(401);
|
Template::throwError(401);
|
||||||
|
|
||||||
$currentUser = $msz->getActiveUser();
|
$forumCtx = $msz->getForumContext();
|
||||||
|
$forumCategories = $forumCtx->getCategories();
|
||||||
|
$forumTopics = $forumCtx->getTopics();
|
||||||
|
$forumPosts = $forumCtx->getPosts();
|
||||||
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
|
||||||
|
$currentUser = $authInfo->getUserInfo();
|
||||||
$currentUserId = $currentUser->getId();
|
$currentUserId = $currentUser->getId();
|
||||||
if($msz->hasActiveBan())
|
if($usersCtx->hasActiveBan($currentUser))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$forum = $msz->getForum();
|
|
||||||
$users = $msz->getUsers();
|
|
||||||
|
|
||||||
$userInfos = [];
|
|
||||||
$userColours = [];
|
|
||||||
$userPostsCounts = [];
|
|
||||||
|
|
||||||
$forumPostingModes = [
|
$forumPostingModes = [
|
||||||
'create', 'edit', 'quote', 'preview',
|
'create', 'edit', 'quote', 'preview',
|
||||||
];
|
];
|
||||||
|
@ -64,7 +64,7 @@ if(empty($postId)) {
|
||||||
$hasPostInfo = false;
|
$hasPostInfo = false;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$postInfo = $forum->getPost(postId: $postId);
|
$postInfo = $forumPosts->getPost(postId: $postId);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ if(empty($topicId)) {
|
||||||
$hasTopicInfo = false;
|
$hasTopicInfo = false;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$topicInfo = $forum->getTopic(topicId: $topicId);
|
$topicInfo = $forumTopics->getTopic(topicId: $topicId);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ if(empty($topicId)) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
|
|
||||||
$forumId = $topicInfo->getCategoryId();
|
$forumId = $topicInfo->getCategoryId();
|
||||||
$originalPostInfo = $forum->getPost(topicInfo: $topicInfo);
|
$originalPostInfo = $forumPosts->getPost(topicInfo: $topicInfo);
|
||||||
$hasTopicInfo = true;
|
$hasTopicInfo = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ if(empty($forumId)) {
|
||||||
$hasCategoryInfo = false;
|
$hasCategoryInfo = false;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$categoryInfo = $forum->getCategory(categoryId: $forumId);
|
$categoryInfo = $forumCategories->getCategory(categoryId: $forumId);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ if(empty($forumId)) {
|
||||||
$hasCategoryInfo = true;
|
$hasCategoryInfo = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$perms = $msz->getAuthInfo()->getPerms('forum', $categoryInfo);
|
$perms = $authInfo->getPerms('forum', $categoryInfo);
|
||||||
|
|
||||||
if($categoryInfo->isArchived()
|
if($categoryInfo->isArchived()
|
||||||
|| (isset($topicInfo) && $topicInfo->isLocked() && !$perms->check(Perm::F_TOPIC_LOCK))
|
|| (isset($topicInfo) && $topicInfo->isLocked() && !$perms->check(Perm::F_TOPIC_LOCK))
|
||||||
|
@ -153,7 +153,7 @@ if(!empty($_POST)) {
|
||||||
$postTimeout = $cfg->getInteger('forum.posting.timeout', 5);
|
$postTimeout = $cfg->getInteger('forum.posting.timeout', 5);
|
||||||
if($postTimeout > 0) {
|
if($postTimeout > 0) {
|
||||||
$postTimeoutThreshold = DateTime::now()->modify(sprintf('-%d seconds', $postTimeout));
|
$postTimeoutThreshold = DateTime::now()->modify(sprintf('-%d seconds', $postTimeout));
|
||||||
$lastPostCreatedAt = $forum->getUserLastPostCreatedAt($currentUser);
|
$lastPostCreatedAt = $forumPosts->getUserLastPostCreatedAt($currentUser);
|
||||||
|
|
||||||
if($lastPostCreatedAt->isMoreThan($postTimeoutThreshold)) {
|
if($lastPostCreatedAt->isMoreThan($postTimeoutThreshold)) {
|
||||||
$waitSeconds = $postTimeout + ($lastPostCreatedAt->getUnixTimeSeconds() - time());
|
$waitSeconds = $postTimeout + ($lastPostCreatedAt->getUnixTimeSeconds() - time());
|
||||||
|
@ -206,7 +206,7 @@ if(!empty($_POST)) {
|
||||||
switch($mode) {
|
switch($mode) {
|
||||||
case 'create':
|
case 'create':
|
||||||
if(empty($topicInfo)) {
|
if(empty($topicInfo)) {
|
||||||
$topicInfo = $forum->createTopic(
|
$topicInfo = $forumTopics->createTopic(
|
||||||
$categoryInfo,
|
$categoryInfo,
|
||||||
$currentUser,
|
$currentUser,
|
||||||
$topicTitle,
|
$topicTitle,
|
||||||
|
@ -214,11 +214,11 @@ if(!empty($_POST)) {
|
||||||
);
|
);
|
||||||
|
|
||||||
$topicId = $topicInfo->getId();
|
$topicId = $topicInfo->getId();
|
||||||
$forum->incrementCategoryTopics($categoryInfo);
|
$forumCategories->incrementCategoryTopics($categoryInfo);
|
||||||
} else
|
} else
|
||||||
$forum->bumpTopic($topicInfo);
|
$forumTopics->bumpTopic($topicInfo);
|
||||||
|
|
||||||
$postInfo = $forum->createPost(
|
$postInfo = $forumPosts->createPost(
|
||||||
$topicId,
|
$topicId,
|
||||||
$currentUser,
|
$currentUser,
|
||||||
$_SERVER['REMOTE_ADDR'],
|
$_SERVER['REMOTE_ADDR'],
|
||||||
|
@ -229,7 +229,7 @@ if(!empty($_POST)) {
|
||||||
);
|
);
|
||||||
|
|
||||||
$postId = $postInfo->getId();
|
$postId = $postInfo->getId();
|
||||||
$forum->incrementCategoryPosts($categoryInfo);
|
$forumCategories->incrementCategoryPosts($categoryInfo);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'edit':
|
case 'edit':
|
||||||
|
@ -237,7 +237,7 @@ if(!empty($_POST)) {
|
||||||
&& $postInfo->shouldMarkAsEdited()
|
&& $postInfo->shouldMarkAsEdited()
|
||||||
&& $postText !== $postInfo->getBody();
|
&& $postText !== $postInfo->getBody();
|
||||||
|
|
||||||
$forum->updatePost(
|
$forumPosts->updatePost(
|
||||||
$postId,
|
$postId,
|
||||||
remoteAddr: $_SERVER['REMOTE_ADDR'],
|
remoteAddr: $_SERVER['REMOTE_ADDR'],
|
||||||
body: $postText,
|
body: $postText,
|
||||||
|
@ -247,7 +247,7 @@ if(!empty($_POST)) {
|
||||||
);
|
);
|
||||||
|
|
||||||
if($isEditingTopic && ($topicTitleChanged || $topicTypeChanged))
|
if($isEditingTopic && ($topicTitleChanged || $topicTypeChanged))
|
||||||
$forum->updateTopic(
|
$forumTopics->updateTopic(
|
||||||
$topicId,
|
$topicId,
|
||||||
title: $topicTitle,
|
title: $topicTitle,
|
||||||
type: $topicType
|
type: $topicType
|
||||||
|
@ -257,12 +257,11 @@ if(!empty($_POST)) {
|
||||||
|
|
||||||
if(empty($notices)) {
|
if(empty($notices)) {
|
||||||
// does this ternary ever return forum-topic?
|
// does this ternary ever return forum-topic?
|
||||||
$redirect = url(empty($topicInfo) ? 'forum-topic' : 'forum-post', [
|
$redirect = $msz->getURLs()->format(empty($topicInfo) ? 'forum-topic' : 'forum-post', [
|
||||||
'topic' => $topicId ?? 0,
|
'topic' => $topicId ?? 0,
|
||||||
'post' => $postId ?? 0,
|
'post' => $postId ?? 0,
|
||||||
'post_fragment' => 'p' . ($postId ?? 0),
|
|
||||||
]);
|
]);
|
||||||
redirect($redirect);
|
Tools::redirect($redirect);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,17 +276,9 @@ if($mode === 'edit') { // $post is pretty much sure to be populated at this poin
|
||||||
$post->info = $postInfo;
|
$post->info = $postInfo;
|
||||||
|
|
||||||
if($postInfo->hasUserId()) {
|
if($postInfo->hasUserId()) {
|
||||||
$postUserId = $postInfo->getUserId();
|
$post->user = $usersCtx->getUserInfo($postInfo->getUserId());
|
||||||
if(!array_key_exists($postUserId, $userInfos)) {
|
$post->colour = $usersCtx->getUserColour($post->user);
|
||||||
$userInfo = $users->getUser($postUserId, 'id');
|
$post->postsCount = $forumCtx->countTotalUserPosts($post->user);
|
||||||
$userInfos[$postUserId] = $userInfo;
|
|
||||||
$userColours[$postUserId] = $users->getUserColour($userInfo);
|
|
||||||
$userPostsCounts[$postUserId] = $forum->countPosts(userInfo: $userInfo, deleted: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
$post->user = $userInfos[$postUserId];
|
|
||||||
$post->colour = $userColours[$postUserId];
|
|
||||||
$post->postsCount = $userPostsCounts[$postUserId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$post->isOriginalPost = $originalPostInfo->getId() == $postInfo->getId();
|
$post->isOriginalPost = $originalPostInfo->getId() == $postInfo->getId();
|
||||||
|
@ -298,18 +289,18 @@ if($mode === 'edit') { // $post is pretty much sure to be populated at this poin
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$lastPostInfo = $forum->getPost(userInfo: $currentUser, getLast: true, deleted: false);
|
$lastPostInfo = $forumPosts->getPost(userInfo: $currentUser, getLast: true, deleted: false);
|
||||||
$selectedParser = $lastPostInfo->getParser();
|
$selectedParser = $lastPostInfo->getParser();
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
$selectedParser = Parser::BBCODE;
|
$selectedParser = Parser::BBCODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
Template::render('forum.posting', [
|
Template::render('forum.posting', [
|
||||||
'posting_breadcrumbs' => $forum->getCategoryAncestry($categoryInfo),
|
'posting_breadcrumbs' => iterator_to_array($forumCategories->getCategoryAncestry($categoryInfo)),
|
||||||
'global_accent_colour' => $forum->getCategoryColour($categoryInfo),
|
'global_accent_colour' => $forumCategories->getCategoryColour($categoryInfo),
|
||||||
'posting_user' => $currentUser,
|
'posting_user' => $currentUser,
|
||||||
'posting_user_colour' => $userColours[$currentUser->getId()] ?? $users->getUserColour($currentUser),
|
'posting_user_colour' => $usersCtx->getUserColour($currentUser),
|
||||||
'posting_user_posts_count' => $userPostsCounts[$currentUser->getId()] ?? $forum->countPosts(userInfo: $currentUser, deleted: false),
|
'posting_user_posts_count' => $forumCtx->countTotalUserPosts($currentUser),
|
||||||
'posting_user_preferred_parser' => $selectedParser,
|
'posting_user_preferred_parser' => $selectedParser,
|
||||||
'posting_forum' => $categoryInfo,
|
'posting_forum' => $categoryInfo,
|
||||||
'posting_notices' => $notices,
|
'posting_notices' => $notices,
|
||||||
|
|
|
@ -4,8 +4,13 @@ namespace Misuzu;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
$forum = $msz->getForum();
|
$urls = $msz->getURLs();
|
||||||
$users = $msz->getUsers();
|
$forumCtx = $msz->getForumContext();
|
||||||
|
$forumCategories = $forumCtx->getCategories();
|
||||||
|
$forumTopics = $forumCtx->getTopics();
|
||||||
|
$forumTopicRedirects = $forumCtx->getTopicRedirects();
|
||||||
|
$forumPosts = $forumCtx->getPosts();
|
||||||
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
|
||||||
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
|
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
|
||||||
$topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0;
|
$topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0;
|
||||||
|
@ -13,25 +18,26 @@ $categoryId = null;
|
||||||
$moderationMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
$moderationMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
||||||
$submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1';
|
$submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1';
|
||||||
|
|
||||||
$currentUser = $msz->getActiveUser();
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
$currentUser = $authInfo->getUserInfo();
|
||||||
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
||||||
|
|
||||||
if($topicId < 1 && $postId > 0) {
|
if($topicId < 1 && $postId > 0) {
|
||||||
try {
|
try {
|
||||||
$postInfo = $forum->getPost(postId: $postId);
|
$postInfo = $forumPosts->getPost(postId: $postId);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$categoryId = $postInfo->getCategoryId();
|
$categoryId = $postInfo->getCategoryId();
|
||||||
$perms = $msz->getAuthInfo()->getPerms('forum', $postInfo->getCategoryId());
|
$perms = $authInfo->getPerms('forum', $postInfo->getCategoryId());
|
||||||
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
|
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
|
||||||
|
|
||||||
if($postInfo->isDeleted() && !$canDeleteAny)
|
if($postInfo->isDeleted() && !$canDeleteAny)
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
|
|
||||||
$topicId = $postInfo->getTopicId();
|
$topicId = $postInfo->getTopicId();
|
||||||
$preceedingPostCount = $forum->countPosts(
|
$preceedingPostCount = $forumPosts->countPosts(
|
||||||
topicInfo: $topicId,
|
topicInfo: $topicId,
|
||||||
upToPostInfo: $postInfo,
|
upToPostInfo: $postInfo,
|
||||||
deleted: $canDeleteAny ? null : false
|
deleted: $canDeleteAny ? null : false
|
||||||
|
@ -40,7 +46,7 @@ if($topicId < 1 && $postId > 0) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$topicIsNuked = $topicIsDeleted = $canDeleteAny = false;
|
$topicIsNuked = $topicIsDeleted = $canDeleteAny = false;
|
||||||
$topicInfo = $forum->getTopic(topicId: $topicId);
|
$topicInfo = $forumTopics->getTopic(topicId: $topicId);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
$topicIsNuked = true;
|
$topicIsNuked = true;
|
||||||
}
|
}
|
||||||
|
@ -50,26 +56,28 @@ if(!$topicIsNuked) {
|
||||||
|
|
||||||
if($categoryId !== (int)$topicInfo->getCategoryId()) {
|
if($categoryId !== (int)$topicInfo->getCategoryId()) {
|
||||||
$categoryId = (int)$topicInfo->getCategoryId();
|
$categoryId = (int)$topicInfo->getCategoryId();
|
||||||
$perms = $msz->getAuthInfo()->getPerms('forum', $topicInfo->getCategoryId());
|
$perms = $authInfo->getPerms('forum', $topicInfo->getCategoryId());
|
||||||
}
|
}
|
||||||
|
|
||||||
if($msz->hasActiveBan())
|
if($usersCtx->hasActiveBan($currentUser))
|
||||||
$perms = $perms->apply(fn($calc) => $calc & (Perm::F_CATEGORY_LIST | Perm::F_CATEGORY_VIEW));
|
$perms = $perms->apply(fn($calc) => $calc & (Perm::F_CATEGORY_LIST | Perm::F_CATEGORY_VIEW));
|
||||||
|
|
||||||
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
|
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(($topicIsNuked || $topicIsDeleted) && $forum->hasTopicRedirect($topicId)) {
|
if($topicIsNuked || $topicIsDeleted) {
|
||||||
$topicRedirectInfo = $forum->getTopicRedirect($topicId);
|
if($forumTopicRedirects->hasTopicRedirect($topicId)) {
|
||||||
Template::set('topic_redir_info', $topicRedirectInfo);
|
$topicRedirectInfo = $forumTopicRedirects->getTopicRedirect($topicId);
|
||||||
|
Template::set('topic_redir_info', $topicRedirectInfo);
|
||||||
|
|
||||||
if($topicIsNuked || !$canDeleteAny) {
|
if($topicIsNuked || !$canDeleteAny) {
|
||||||
if(empty($topicRedirectInfo))
|
header('Location: ' . $topicRedirectInfo->getLinkTarget());
|
||||||
Template::throwError(404);
|
return;
|
||||||
|
}
|
||||||
header('Location: ' . $topicRedirectInfo->getLinkTarget());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(empty($topicRedirectInfo))
|
||||||
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$perms->check(Perm::F_CATEGORY_VIEW))
|
if(!$perms->check(Perm::F_CATEGORY_VIEW))
|
||||||
|
@ -79,7 +87,7 @@ if(!$perms->check(Perm::F_CATEGORY_VIEW))
|
||||||
// this should be in the config
|
// this should be in the config
|
||||||
$deletePostThreshold = 1;
|
$deletePostThreshold = 1;
|
||||||
|
|
||||||
$categoryInfo = $forum->getCategory(topicInfo: $topicInfo);
|
$categoryInfo = $forumCategories->getCategory(topicInfo: $topicInfo);
|
||||||
$topicIsLocked = $topicInfo->isLocked();
|
$topicIsLocked = $topicInfo->isLocked();
|
||||||
$topicIsArchived = $categoryInfo->isArchived();
|
$topicIsArchived = $categoryInfo->isArchived();
|
||||||
$topicPostsTotal = $topicInfo->getTotalPostsCount();
|
$topicPostsTotal = $topicInfo->getTotalPostsCount();
|
||||||
|
@ -106,10 +114,11 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
if(!CSRF::validateRequest())
|
if(!CSRF::validateRequest())
|
||||||
Template::displayInfo("Couldn't verify this request, please refresh the page and try again.", 403);
|
Template::displayInfo("Couldn't verify this request, please refresh the page and try again.", 403);
|
||||||
|
|
||||||
if(!$msz->isLoggedIn())
|
$authInfo = $authInfo;
|
||||||
|
if(!$authInfo->isLoggedIn())
|
||||||
Template::displayInfo('You must be logged in to manage posts.', 401);
|
Template::displayInfo('You must be logged in to manage posts.', 401);
|
||||||
|
|
||||||
if($msz->hasActiveBan())
|
if($usersCtx->hasActiveBan($currentUser))
|
||||||
Template::displayInfo('You have been banned, check your profile for more information.', 403);
|
Template::displayInfo('You have been banned, check your profile for more information.', 403);
|
||||||
|
|
||||||
switch($moderationMode) {
|
switch($moderationMode) {
|
||||||
|
@ -133,7 +142,7 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
Template::displayInfo('This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403);
|
Template::displayInfo('This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403);
|
||||||
|
|
||||||
// deleted posts are intentionally included
|
// deleted posts are intentionally included
|
||||||
$topicPostCount = $forum->countPosts(topicInfo: $topicInfo);
|
$topicPostCount = $forumPosts->countPosts(topicInfo: $topicInfo);
|
||||||
if($topicPostCount > $deletePostThreshold)
|
if($topicPostCount > $deletePostThreshold)
|
||||||
Template::displayInfo('This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.', 403);
|
Template::displayInfo('This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.', 403);
|
||||||
}
|
}
|
||||||
|
@ -150,19 +159,19 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
} elseif(!$submissionConfirmed) {
|
} elseif(!$submissionConfirmed) {
|
||||||
url_redirect(
|
Tools::redirect($urls->format(
|
||||||
'forum-topic',
|
'forum-topic',
|
||||||
['topic' => $topicInfo->getId()]
|
['topic' => $topicInfo->getId()]
|
||||||
);
|
));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$forum->deleteTopic($topicInfo->getId());
|
$forumTopics->deleteTopic($topicInfo->getId());
|
||||||
$msz->createAuditLog('FORUM_TOPIC_DELETE', [$topicInfo->getId()]);
|
$msz->createAuditLog('FORUM_TOPIC_DELETE', [$topicInfo->getId()]);
|
||||||
|
|
||||||
url_redirect('forum-category', [
|
Tools::redirect($urls->format('forum-category', [
|
||||||
'forum' => $categoryInfo->getId(),
|
'forum' => $categoryInfo->getId(),
|
||||||
]);
|
]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'restore':
|
case 'restore':
|
||||||
|
@ -181,18 +190,18 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
} elseif(!$submissionConfirmed) {
|
} elseif(!$submissionConfirmed) {
|
||||||
url_redirect('forum-topic', [
|
Tools::redirect($urls->format('forum-topic', [
|
||||||
'topic' => $topicInfo->getId(),
|
'topic' => $topicInfo->getId(),
|
||||||
]);
|
]));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$forum->restoreTopic($topicInfo->getId());
|
$forumTopics->restoreTopic($topicInfo->getId());
|
||||||
$msz->createAuditLog('FORUM_TOPIC_RESTORE', [$topicInfo->getId()]);
|
$msz->createAuditLog('FORUM_TOPIC_RESTORE', [$topicInfo->getId()]);
|
||||||
|
|
||||||
url_redirect('forum-category', [
|
Tools::redirect($urls->format('forum-category', [
|
||||||
'forum' => $categoryInfo->getId(),
|
'forum' => $categoryInfo->getId(),
|
||||||
]);
|
]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'nuke':
|
case 'nuke':
|
||||||
|
@ -211,51 +220,51 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
} elseif(!$submissionConfirmed) {
|
} elseif(!$submissionConfirmed) {
|
||||||
url_redirect('forum-topic', [
|
Tools::redirect($urls->format('forum-topic', [
|
||||||
'topic' => $topicInfo->getId(),
|
'topic' => $topicInfo->getId(),
|
||||||
]);
|
]));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$forum->nukeTopic($topicInfo->getId());
|
$forumTopics->nukeTopic($topicInfo->getId());
|
||||||
$msz->createAuditLog('FORUM_TOPIC_NUKE', [$topicInfo->getId()]);
|
$msz->createAuditLog('FORUM_TOPIC_NUKE', [$topicInfo->getId()]);
|
||||||
|
|
||||||
url_redirect('forum-category', [
|
Tools::redirect($urls->format('forum-category', [
|
||||||
'forum' => $categoryInfo->getId(),
|
'forum' => $categoryInfo->getId(),
|
||||||
]);
|
]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'bump':
|
case 'bump':
|
||||||
if($canBumpTopic) {
|
if($canBumpTopic) {
|
||||||
$forum->bumpTopic($topicInfo->getId());
|
$forumTopics->bumpTopic($topicInfo->getId());
|
||||||
$msz->createAuditLog('FORUM_TOPIC_BUMP', [$topicInfo->getId()]);
|
$msz->createAuditLog('FORUM_TOPIC_BUMP', [$topicInfo->getId()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('forum-topic', [
|
Tools::redirect($urls->format('forum-topic', [
|
||||||
'topic' => $topicInfo->getId(),
|
'topic' => $topicInfo->getId(),
|
||||||
]);
|
]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'lock':
|
case 'lock':
|
||||||
if($canLockTopic && !$topicIsLocked) {
|
if($canLockTopic && !$topicIsLocked) {
|
||||||
$forum->lockTopic($topicInfo->getId());
|
$forumTopics->lockTopic($topicInfo->getId());
|
||||||
$msz->createAuditLog('FORUM_TOPIC_LOCK', [$topicInfo->getId()]);
|
$msz->createAuditLog('FORUM_TOPIC_LOCK', [$topicInfo->getId()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('forum-topic', [
|
Tools::redirect($urls->format('forum-topic', [
|
||||||
'topic' => $topicInfo->getId(),
|
'topic' => $topicInfo->getId(),
|
||||||
]);
|
]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'unlock':
|
case 'unlock':
|
||||||
if($canLockTopic && $topicIsLocked) {
|
if($canLockTopic && $topicIsLocked) {
|
||||||
$forum->unlockTopic($topicInfo->getId());
|
$forumTopics->unlockTopic($topicInfo->getId());
|
||||||
$msz->createAuditLog('FORUM_TOPIC_UNLOCK', [$topicInfo->getId()]);
|
$msz->createAuditLog('FORUM_TOPIC_UNLOCK', [$topicInfo->getId()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('forum-topic', [
|
Tools::redirect($urls->format('forum-topic', [
|
||||||
'topic' => $topicInfo->getId(),
|
'topic' => $topicInfo->getId(),
|
||||||
]);
|
]));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -273,7 +282,7 @@ if(isset($preceedingPostCount))
|
||||||
if(!$topicPagination->hasValidOffset())
|
if(!$topicPagination->hasValidOffset())
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
|
|
||||||
$postInfos = $forum->getPosts(
|
$postInfos = $forumPosts->getPosts(
|
||||||
topicInfo: $topicInfo,
|
topicInfo: $topicInfo,
|
||||||
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false,
|
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false,
|
||||||
pagination: $topicPagination,
|
pagination: $topicPagination,
|
||||||
|
@ -282,11 +291,12 @@ $postInfos = $forum->getPosts(
|
||||||
if(empty($postInfos))
|
if(empty($postInfos))
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
|
|
||||||
$originalPostInfo = $forum->getPost(topicInfo: $topicInfo);
|
try {
|
||||||
|
$originalPostInfo = $forumPosts->getPost(topicInfo: $topicInfo);
|
||||||
|
} catch(RuntimeException $ex) {
|
||||||
|
Template::throwError(404);
|
||||||
|
}
|
||||||
|
|
||||||
$userInfos = [];
|
|
||||||
$userColours = [];
|
|
||||||
$userPostsCounts = [];
|
|
||||||
$posts = [];
|
$posts = [];
|
||||||
|
|
||||||
foreach($postInfos as $postInfo) {
|
foreach($postInfos as $postInfo) {
|
||||||
|
@ -294,17 +304,9 @@ foreach($postInfos as $postInfo) {
|
||||||
$post->info = $postInfo;
|
$post->info = $postInfo;
|
||||||
|
|
||||||
if($postInfo->hasUserId()) {
|
if($postInfo->hasUserId()) {
|
||||||
$postUserId = $postInfo->getUserId();
|
$post->user = $usersCtx->getUserInfo($postInfo->getUserId());
|
||||||
if(!array_key_exists($postUserId, $userInfos)) {
|
$post->colour = $usersCtx->getUserColour($post->user);
|
||||||
$userInfo = $users->getUser($postUserId, 'id');
|
$post->postsCount = $forumCtx->countTotalUserPosts($post->user);
|
||||||
$userInfos[$postUserId] = $userInfo;
|
|
||||||
$userColours[$postUserId] = $users->getUserColour($userInfo);
|
|
||||||
$userPostsCounts[$postUserId] = $forum->countPosts(userInfo: $userInfo, deleted: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
$post->user = $userInfos[$postUserId];
|
|
||||||
$post->colour = $userColours[$postUserId];
|
|
||||||
$post->postsCount = $userPostsCounts[$postUserId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$post->isOriginalPost = $originalPostInfo->getId() == $postInfo->getId();
|
$post->isOriginalPost = $originalPostInfo->getId() == $postInfo->getId();
|
||||||
|
@ -314,10 +316,10 @@ foreach($postInfos as $postInfo) {
|
||||||
|
|
||||||
$canReply = !$topicIsArchived && !$topicIsLocked && !$topicIsDeleted && $perms->check(Perm::F_POST_CREATE);
|
$canReply = !$topicIsArchived && !$topicIsLocked && !$topicIsDeleted && $perms->check(Perm::F_POST_CREATE);
|
||||||
|
|
||||||
if(!$forum->checkUserHasReadTopic($userInfo, $topicInfo))
|
if(!$forumTopics->checkUserHasReadTopic($currentUser, $topicInfo))
|
||||||
$forum->incrementTopicView($topicInfo);
|
$forumTopics->incrementTopicViews($topicInfo);
|
||||||
|
|
||||||
$forum->updateUserReadTopic($currentUser, $topicInfo);
|
$forumTopics->updateUserReadTopic($currentUser, $topicInfo);
|
||||||
|
|
||||||
$perms = $perms->checkMany([
|
$perms = $perms->checkMany([
|
||||||
'can_create_post' => Perm::F_POST_CREATE,
|
'can_create_post' => Perm::F_POST_CREATE,
|
||||||
|
@ -328,8 +330,8 @@ $perms = $perms->checkMany([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Template::render('forum.topic', [
|
Template::render('forum.topic', [
|
||||||
'topic_breadcrumbs' => $forum->getCategoryAncestry($topicInfo),
|
'topic_breadcrumbs' => iterator_to_array($forumCategories->getCategoryAncestry($topicInfo)),
|
||||||
'global_accent_colour' => $forum->getCategoryColour($topicInfo),
|
'global_accent_colour' => $forumCategories->getCategoryColour($topicInfo),
|
||||||
'topic_info' => $topicInfo,
|
'topic_info' => $topicInfo,
|
||||||
'category_info' => $categoryInfo,
|
'category_info' => $categoryInfo,
|
||||||
'topic_posts' => $posts,
|
'topic_posts' => $posts,
|
||||||
|
|
|
@ -7,13 +7,15 @@ use Index\DateTime;
|
||||||
use Index\XArray;
|
use Index\XArray;
|
||||||
use Misuzu\Changelog\Changelog;
|
use Misuzu\Changelog\Changelog;
|
||||||
|
|
||||||
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_CHANGES_MANAGE))
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!$authInfo->getPerms('global')->check(Perm::G_CL_CHANGES_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$changeActions = [];
|
$changeActions = [];
|
||||||
foreach(Changelog::ACTIONS as $action)
|
foreach(Changelog::ACTIONS as $action)
|
||||||
$changeActions[$action] = Changelog::actionText($action);
|
$changeActions[$action] = Changelog::actionText($action);
|
||||||
|
|
||||||
|
$urls = $msz->getURLs();
|
||||||
$changelog = $msz->getChangelog();
|
$changelog = $msz->getChangelog();
|
||||||
$changeId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
|
$changeId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$changeInfo = null;
|
$changeInfo = null;
|
||||||
|
@ -37,7 +39,7 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||||
|
|
||||||
$changelog->deleteChange($changeInfo);
|
$changelog->deleteChange($changeInfo);
|
||||||
$msz->createAuditLog('CHANGELOG_ENTRY_DELETE', [$changeInfo->getId()]);
|
$msz->createAuditLog('CHANGELOG_ENTRY_DELETE', [$changeInfo->getId()]);
|
||||||
url_redirect('manage-changelog-changes');
|
Tools::redirect($urls->format('manage-changelog-changes'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +105,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
[$changeInfo->getId()]
|
[$changeInfo->getId()]
|
||||||
);
|
);
|
||||||
|
|
||||||
url_redirect('manage-changelog-change', ['change' => $changeInfo->getId()]);
|
Tools::redirect($urls->format('manage-changelog-change', ['change' => $changeInfo->getId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,5 +115,5 @@ Template::render('manage.changelog.change', [
|
||||||
'change_info_tags' => $changeTagIds,
|
'change_info_tags' => $changeTagIds,
|
||||||
'change_tags' => $tagInfos,
|
'change_tags' => $tagInfos,
|
||||||
'change_actions' => $changeActions,
|
'change_actions' => $changeActions,
|
||||||
'change_author_id' => $msz->getActiveUser()->getId(),
|
'change_author_id' => $authInfo->getUserInfo()->getId(),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -7,6 +7,8 @@ if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_CHANGES_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$changelog = $msz->getChangelog();
|
$changelog = $msz->getChangelog();
|
||||||
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
|
||||||
$changelogPagination = new Pagination($changelog->countChanges(), 30);
|
$changelogPagination = new Pagination($changelog->countChanges(), 30);
|
||||||
|
|
||||||
if(!$changelogPagination->hasValidOffset())
|
if(!$changelogPagination->hasValidOffset())
|
||||||
|
@ -14,30 +16,15 @@ if(!$changelogPagination->hasValidOffset())
|
||||||
|
|
||||||
$changeInfos = $changelog->getChanges(pagination: $changelogPagination);
|
$changeInfos = $changelog->getChanges(pagination: $changelogPagination);
|
||||||
$changes = [];
|
$changes = [];
|
||||||
$userInfos = [];
|
|
||||||
$userColours = [];
|
|
||||||
|
|
||||||
foreach($changeInfos as $changeInfo) {
|
foreach($changeInfos as $changeInfo) {
|
||||||
$userId = $changeInfo->getUserId();
|
$userInfo = $changeInfo->hasUserId() ? $usersCtx->getUserInfo($changeInfo->getUserId()) : null;
|
||||||
|
|
||||||
if(array_key_exists($userId, $userInfos)) {
|
|
||||||
$userInfo = $userInfos[$userId];
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
$userInfo = $users->getUser($userId, 'id');
|
|
||||||
$userColours[$userId] = $users->getUserColour($userInfo);
|
|
||||||
} catch(RuntimeException $ex) {
|
|
||||||
$userInfo = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$userInfos[$userId] = $userInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
$changes[] = [
|
$changes[] = [
|
||||||
'change' => $changeInfo,
|
'change' => $changeInfo,
|
||||||
'tags' => $changelog->getTags(changeInfo: $changeInfo),
|
'tags' => $changelog->getTags(changeInfo: $changeInfo),
|
||||||
'user' => $userInfo,
|
'user' => $userInfo,
|
||||||
'user_colour' => $userColours[$userId] ?? \Index\Colour\Colour::none(),
|
'user_colour' => $usersCtx->getUserColour($userInfo),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ use RuntimeException;
|
||||||
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_TAGS_MANAGE))
|
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_TAGS_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
|
$urls = $msz->getURLs();
|
||||||
$changelog = $msz->getChangelog();
|
$changelog = $msz->getChangelog();
|
||||||
$tagId = (string)filter_input(INPUT_GET, 't', FILTER_SANITIZE_NUMBER_INT);
|
$tagId = (string)filter_input(INPUT_GET, 't', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$loadTagInfo = fn() => $changelog->getTag($tagId);
|
$loadTagInfo = fn() => $changelog->getTag($tagId);
|
||||||
|
@ -26,7 +27,7 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||||
|
|
||||||
$changelog->deleteTag($tagInfo);
|
$changelog->deleteTag($tagInfo);
|
||||||
$msz->createAuditLog('CHANGELOG_TAG_DELETE', [$tagInfo->getId()]);
|
$msz->createAuditLog('CHANGELOG_TAG_DELETE', [$tagInfo->getId()]);
|
||||||
url_redirect('manage-changelog-tags');
|
Tools::redirect($urls->format('manage-changelog-tags'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
);
|
);
|
||||||
|
|
||||||
if($isNew) {
|
if($isNew) {
|
||||||
url_redirect('manage-changelog-tag', ['tag' => $tagInfo->getId()]);
|
Tools::redirect($urls->format('manage-changelog-tag', ['tag' => $tagInfo->getId()]));
|
||||||
return;
|
return;
|
||||||
} else $tagInfo = $loadTagInfo();
|
} else $tagInfo = $loadTagInfo();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_FORUM_TOPIC_REDIRS_MANAGE))
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!$authInfo->getPerms('global')->check(Perm::G_FORUM_TOPIC_REDIRS_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$forum = $msz->getForum();
|
$urls = $msz->getURLs();
|
||||||
|
$forumCtx = $msz->getForumContext();
|
||||||
|
$forumTopicRedirects = $forumCtx->getTopicRedirects();
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
if(!CSRF::validateRequest())
|
if(!CSRF::validateRequest())
|
||||||
|
@ -14,8 +17,8 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$rTopicURL = trim((string)filter_input(INPUT_POST, 'topic_redir_url'));
|
$rTopicURL = trim((string)filter_input(INPUT_POST, 'topic_redir_url'));
|
||||||
|
|
||||||
$msz->createAuditLog('FORUM_TOPIC_REDIR_CREATE', [$rTopicId]);
|
$msz->createAuditLog('FORUM_TOPIC_REDIR_CREATE', [$rTopicId]);
|
||||||
$forum->createTopicRedirect($rTopicId, $msz->getActiveUser(), $rTopicURL);
|
$forumTopicRedirects->createTopicRedirect($rTopicId, $authInfo->getUserInfo(), $rTopicURL);
|
||||||
url_redirect('manage-forum-topic-redirs');
|
Tools::redirect($urls->format('manage-forum-topic-redirs'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,16 +28,16 @@ if(filter_input(INPUT_GET, 'm') === 'explode') {
|
||||||
|
|
||||||
$rTopicId = (string)filter_input(INPUT_GET, 't');
|
$rTopicId = (string)filter_input(INPUT_GET, 't');
|
||||||
$msz->createAuditLog('FORUM_TOPIC_REDIR_REMOVE', [$rTopicId]);
|
$msz->createAuditLog('FORUM_TOPIC_REDIR_REMOVE', [$rTopicId]);
|
||||||
$forum->deleteTopicRedirect($rTopicId);
|
$forumTopicRedirects->deleteTopicRedirect($rTopicId);
|
||||||
url_redirect('manage-forum-topic-redirs');
|
Tools::redirect($urls->format('manage-forum-topic-redirs'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$pagination = new Pagination($forum->countTopicRedirects(), 20);
|
$pagination = new Pagination($forumTopicRedirects->countTopicRedirects(), 20);
|
||||||
if(!$pagination->hasValidOffset())
|
if(!$pagination->hasValidOffset())
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
|
|
||||||
$redirs = $forum->getTopicRedirects(pagination: $pagination);
|
$redirs = $forumTopicRedirects->getTopicRedirects(pagination: $pagination);
|
||||||
|
|
||||||
Template::render('manage.forum.redirs', [
|
Template::render('manage.forum.redirs', [
|
||||||
'manage_redirs' => $redirs,
|
'manage_redirs' => $redirs,
|
||||||
|
|
|
@ -9,6 +9,8 @@ if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_EMOTES_MANAGE))
|
||||||
|
|
||||||
$emotes = $msz->getEmotes();
|
$emotes = $msz->getEmotes();
|
||||||
$emoteId = (string)filter_input(INPUT_GET, 'e', FILTER_SANITIZE_NUMBER_INT);
|
$emoteId = (string)filter_input(INPUT_GET, 'e', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
$emoteInfo = [];
|
||||||
|
$emoteStrings = [];
|
||||||
|
|
||||||
if(empty($emoteId))
|
if(empty($emoteId))
|
||||||
$isNew = true;
|
$isNew = true;
|
||||||
|
@ -16,7 +18,7 @@ else
|
||||||
try {
|
try {
|
||||||
$isNew = false;
|
$isNew = false;
|
||||||
$emoteInfo = $emotes->getEmote($emoteId);
|
$emoteInfo = $emotes->getEmote($emoteId);
|
||||||
$emoteStrings = $emotes->getEmoteStrings($emoteInfo);
|
$emoteStrings = iterator_to_array($emotes->getEmoteStrings($emoteInfo));
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
@ -95,12 +97,12 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
[$emoteInfo->getId()]
|
[$emoteInfo->getId()]
|
||||||
);
|
);
|
||||||
|
|
||||||
url_redirect('manage-general-emoticon', ['emote' => $emoteInfo->getId()]);
|
Tools::redirect($msz->getURLs()->format('manage-general-emoticon', ['emote' => $emoteInfo->getId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Template::render('manage.general.emoticon', [
|
Template::render('manage.general.emoticon', [
|
||||||
'emote_new' => $isNew,
|
'emote_new' => $isNew,
|
||||||
'emote_info' => $emoteInfo ?? null,
|
'emote_info' => $emoteInfo,
|
||||||
'emote_strings' => $emoteStrings ?? [],
|
'emote_strings' => $emoteStrings,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -23,7 +23,7 @@ if(CSRF::validateRequest() && !empty($_GET['emote'])) {
|
||||||
} else {
|
} else {
|
||||||
if(isset($_GET['order'])) {
|
if(isset($_GET['order'])) {
|
||||||
$order = filter_input(INPUT_GET, 'order');
|
$order = filter_input(INPUT_GET, 'order');
|
||||||
$offset = $order === 'i' ? 1 : ($order === 'd' ? -1 : 0);
|
$offset = $order === 'i' ? 10 : ($order === 'd' ? -10 : 0);
|
||||||
$emotes->updateEmoteOrderOffset($emoteInfo, $offset);
|
$emotes->updateEmoteOrderOffset($emoteInfo, $offset);
|
||||||
$msz->createAuditLog('EMOTICON_ORDER', [$emoteInfo->getId()]);
|
$msz->createAuditLog('EMOTICON_ORDER', [$emoteInfo->getId()]);
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ if(CSRF::validateRequest() && !empty($_GET['emote'])) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('manage-general-emoticons');
|
Tools::redirect($msz->getURLs()->format('manage-general-emoticons'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,14 @@ use Misuzu\Pagination;
|
||||||
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_LOGS_VIEW))
|
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_LOGS_VIEW))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$usersCtx = $msz->getUsersContext();
|
||||||
$auditLog = $msz->getAuditLog();
|
$auditLog = $msz->getAuditLog();
|
||||||
$pagination = new Pagination($auditLog->countLogs(), 50);
|
$pagination = new Pagination($auditLog->countLogs(), 50);
|
||||||
|
|
||||||
if(!$pagination->hasValidOffset())
|
if(!$pagination->hasValidOffset())
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
|
|
||||||
$logs = $auditLog->getLogs(pagination: $pagination);
|
$logs = iterator_to_array($auditLog->getLogs(pagination: $pagination));
|
||||||
$userInfos = [];
|
$userInfos = [];
|
||||||
$userColours = [];
|
$userColours = [];
|
||||||
|
|
||||||
|
@ -21,8 +21,8 @@ foreach($logs as $log)
|
||||||
if($log->hasUserId()) {
|
if($log->hasUserId()) {
|
||||||
$userId = $log->getUserId();
|
$userId = $log->getUserId();
|
||||||
if(!array_key_exists($userId, $userInfos)) {
|
if(!array_key_exists($userId, $userInfos)) {
|
||||||
$userInfos[$userId] = $users->getUser($userId, 'id');
|
$userInfos[$userId] = $usersCtx->getUserInfo($userId);
|
||||||
$userColours[$userId] = $users->getUserColour($userInfos[$userId]);
|
$userColours[$userId] = $usersCtx->getUserColour($userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\Config\CfgTools;
|
|
||||||
|
|
||||||
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CONFIG_MANAGE))
|
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CONFIG_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
|
@ -15,7 +13,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$valueName = $valueInfo->getName();
|
$valueName = $valueInfo->getName();
|
||||||
$msz->createAuditLog('CONFIG_DELETE', [$valueName]);
|
$msz->createAuditLog('CONFIG_DELETE', [$valueName]);
|
||||||
$cfg->removeValues($valueName);
|
$cfg->removeValues($valueName);
|
||||||
url_redirect('manage-general-settings');
|
Tools::redirect($msz->getURLs()->format('manage-general-settings'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\Config\DbConfig;
|
use Syokuhou\DbConfig;
|
||||||
|
|
||||||
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CONFIG_MANAGE))
|
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CONFIG_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
@ -73,7 +73,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
|
|
||||||
$msz->createAuditLog($isNew ? 'CONFIG_CREATE' : 'CONFIG_UPDATE', [$sName]);
|
$msz->createAuditLog($isNew ? 'CONFIG_CREATE' : 'CONFIG_UPDATE', [$sName]);
|
||||||
$applyFunc($sName, $sValue);
|
$applyFunc($sName, $sValue);
|
||||||
url_redirect('manage-general-settings');
|
Tools::redirect($msz->getURLs()->format('manage-general-settings'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Misuzu;
|
|
||||||
|
|
||||||
url_redirect('manage-general-overview');
|
|
|
@ -6,6 +6,7 @@ use RuntimeException;
|
||||||
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_NEWS_CATEGORIES_MANAGE))
|
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_NEWS_CATEGORIES_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
|
$urls = $msz->getURLs();
|
||||||
$news = $msz->getNews();
|
$news = $msz->getNews();
|
||||||
$categoryId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
|
$categoryId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$loadCategoryInfo = fn() => $news->getCategory(categoryId: $categoryId);
|
$loadCategoryInfo = fn() => $news->getCategory(categoryId: $categoryId);
|
||||||
|
@ -26,7 +27,7 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||||
|
|
||||||
$news->deleteCategory($categoryInfo);
|
$news->deleteCategory($categoryInfo);
|
||||||
$msz->createAuditLog('NEWS_CATEGORY_DELETE', [$categoryInfo->getId()]);
|
$msz->createAuditLog('NEWS_CATEGORY_DELETE', [$categoryInfo->getId()]);
|
||||||
url_redirect('manage-news-categories');
|
Tools::redirect($urls->format('manage-news-categories'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
);
|
);
|
||||||
|
|
||||||
if($isNew) {
|
if($isNew) {
|
||||||
url_redirect('manage-news-category', ['category' => $categoryInfo->getId()]);
|
Tools::redirect($urls->format('manage-news-category', ['category' => $categoryInfo->getId()]));
|
||||||
return;
|
return;
|
||||||
} else $categoryInfo = $loadCategoryInfo();
|
} else $categoryInfo = $loadCategoryInfo();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Misuzu;
|
|
||||||
|
|
||||||
url_redirect('manage-news-categories');
|
|
|
@ -3,9 +3,11 @@ namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_NEWS_POSTS_MANAGE))
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!$authInfo->getPerms('global')->check(Perm::G_NEWS_POSTS_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
|
$urls = $msz->getURLs();
|
||||||
$news = $msz->getNews();
|
$news = $msz->getNews();
|
||||||
$postId = (string)filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT);
|
$postId = (string)filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$loadPostInfo = fn() => $news->getPost($postId);
|
$loadPostInfo = fn() => $news->getPost($postId);
|
||||||
|
@ -26,7 +28,7 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||||
|
|
||||||
$news->deletePost($postInfo);
|
$news->deletePost($postInfo);
|
||||||
$msz->createAuditLog('NEWS_POST_DELETE', [$postInfo->getId()]);
|
$msz->createAuditLog('NEWS_POST_DELETE', [$postInfo->getId()]);
|
||||||
url_redirect('manage-news-posts');
|
Tools::redirect($urls->format('manage-news-posts'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +39,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$body = trim((string)filter_input(INPUT_POST, 'np_body'));
|
$body = trim((string)filter_input(INPUT_POST, 'np_body'));
|
||||||
|
|
||||||
if($isNew) {
|
if($isNew) {
|
||||||
$postInfo = $news->createPost($category, $title, $body, $featured, $msz->getActiveUser());
|
$postInfo = $news->createPost($category, $title, $body, $featured, $authInfo->getUserInfo());
|
||||||
} else {
|
} else {
|
||||||
if($category === $postInfo->getCategoryId())
|
if($category === $postInfo->getCategoryId())
|
||||||
$category = null;
|
$category = null;
|
||||||
|
@ -62,7 +64,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
// Twitter integration used to be here, replace with Railgun Pulse integration
|
// Twitter integration used to be here, replace with Railgun Pulse integration
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('manage-news-post', ['post' => $postInfo->getId()]);
|
Tools::redirect($urls->format('manage-news-post', ['post' => $postInfo->getId()]));
|
||||||
return;
|
return;
|
||||||
} else $postInfo = $loadPostInfo();
|
} else $postInfo = $loadPostInfo();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -13,7 +13,7 @@ $pagination = new Pagination($news->countPosts(
|
||||||
if(!$pagination->hasValidOffset())
|
if(!$pagination->hasValidOffset())
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
|
|
||||||
$posts = $news->getAllPosts(
|
$posts = $news->getPosts(
|
||||||
includeScheduled: true,
|
includeScheduled: true,
|
||||||
includeDeleted: true,
|
includeDeleted: true,
|
||||||
pagination: $pagination
|
pagination: $pagination
|
||||||
|
|
|
@ -5,10 +5,13 @@ use DateTimeInterface;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Index\DateTime;
|
use Index\DateTime;
|
||||||
|
|
||||||
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_BANS_MANAGE))
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!$authInfo->getPerms('user')->check(Perm::U_BANS_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$bans = $msz->getBans();
|
$urls = $msz->getURLs();
|
||||||
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$bans = $usersCtx->getBans();
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) {
|
if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) {
|
||||||
if(!CSRF::validateRequest())
|
if(!CSRF::validateRequest())
|
||||||
|
@ -22,19 +25,17 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete'))
|
||||||
|
|
||||||
$bans->deleteBans($banInfo);
|
$bans->deleteBans($banInfo);
|
||||||
$msz->createAuditLog('BAN_DELETE', [$banInfo->getId(), $banInfo->getUserId()]);
|
$msz->createAuditLog('BAN_DELETE', [$banInfo->getId(), $banInfo->getUserId()]);
|
||||||
url_redirect('manage-users-bans', ['user' => $banInfo->getUserId()]);
|
Tools::redirect($urls->format('manage-users-bans', ['user' => $banInfo->getUserId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$userInfo = $users->getUser(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id');
|
$userInfo = $usersCtx->getUserInfo(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id');
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$modInfo = $msz->getActiveUser();
|
$modInfo = $authInfo->getUserInfo();
|
||||||
|
|
||||||
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$expires = (int)filter_input(INPUT_POST, 'ub_expires', FILTER_SANITIZE_NUMBER_INT);
|
$expires = (int)filter_input(INPUT_POST, 'ub_expires', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
@ -69,7 +70,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
);
|
);
|
||||||
|
|
||||||
$msz->createAuditLog('BAN_CREATE', [$banInfo->getId(), $userInfo->getId()]);
|
$msz->createAuditLog('BAN_CREATE', [$banInfo->getId(), $userInfo->getId()]);
|
||||||
url_redirect('manage-users-bans', ['user' => $userInfo->getId()]);
|
Tools::redirect($urls->format('manage-users-bans', ['user' => $userInfo->getId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,28 +6,19 @@ use RuntimeException;
|
||||||
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_BANS_MANAGE))
|
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_BANS_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$bans = $usersCtx->getBans();
|
||||||
$userInfos = [
|
|
||||||
$msz->getActiveUser()->getId() => $msz->getActiveUser(),
|
|
||||||
];
|
|
||||||
$userColours = [
|
|
||||||
$msz->getActiveUser()->getId() => $users->getUserColour($msz->getActiveUser()),
|
|
||||||
];
|
|
||||||
|
|
||||||
$filterUser = null;
|
$filterUser = null;
|
||||||
if(filter_has_var(INPUT_GET, 'u')) {
|
if(filter_has_var(INPUT_GET, 'u')) {
|
||||||
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
|
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
|
||||||
try {
|
try {
|
||||||
$filterUser = $users->getUser($filterUserId, 'id');
|
$filterUser = $usersCtx->getUserInfo($filterUserId);
|
||||||
$userInfos[$filterUserId] = $filterUser;
|
|
||||||
$userColours[$filterUserId] = $users->getUserColour($filterUser);
|
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$bans = $msz->getBans();
|
|
||||||
$pagination = new Pagination($bans->countBans(userInfo: $filterUser), 10);
|
$pagination = new Pagination($bans->countBans(userInfo: $filterUser), 10);
|
||||||
|
|
||||||
if(!$pagination->hasValidOffset())
|
if(!$pagination->hasValidOffset())
|
||||||
|
@ -37,29 +28,15 @@ $banList = [];
|
||||||
$banInfos = $bans->getBans(userInfo: $filterUser, activeFirst: true, pagination: $pagination);
|
$banInfos = $bans->getBans(userInfo: $filterUser, activeFirst: true, pagination: $pagination);
|
||||||
|
|
||||||
foreach($banInfos as $banInfo) {
|
foreach($banInfos as $banInfo) {
|
||||||
if(array_key_exists($banInfo->getUserId(), $userInfos))
|
$userInfo = $usersCtx->getUserInfo($banInfo->getUserId());
|
||||||
$userInfo = $userInfos[$banInfo->getUserId()];
|
$userColour = $usersCtx->getUserColour($userInfo);
|
||||||
else
|
|
||||||
$userInfos[$banInfo->getUserId()] = $userInfo = $users->getUser($banInfo->getUserId(), 'id');
|
|
||||||
|
|
||||||
if(array_key_exists($userInfo->getId(), $userColours))
|
|
||||||
$userColour = $userColours[$userInfo->getId()];
|
|
||||||
else
|
|
||||||
$userColours[$userInfo->getId()] = $userColour = $users->getUserColour($userInfo);
|
|
||||||
|
|
||||||
if(!$banInfo->hasModId()) {
|
if(!$banInfo->hasModId()) {
|
||||||
$modInfo = null;
|
$modInfo = null;
|
||||||
$modColour = null;
|
$modColour = null;
|
||||||
} else {
|
} else {
|
||||||
if(array_key_exists($banInfo->getModId(), $userInfos))
|
$modInfo = $usersCtx->getUserInfo($banInfo->getModId());
|
||||||
$modInfo = $userInfos[$banInfo->getModId()];
|
$modColour = $usersCtx->getUserColour($modInfo);
|
||||||
else
|
|
||||||
$userInfos[$banInfo->getModId()] = $modInfo = $users->getUser($banInfo->getModId(), 'id');
|
|
||||||
|
|
||||||
if(array_key_exists($modInfo->getId(), $userColours))
|
|
||||||
$modColour = $userColours[$modInfo->getId()];
|
|
||||||
else
|
|
||||||
$userColours[$modInfo->getId()] = $modColour = $users->getUserColour($modInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$banList[] = [
|
$banList[] = [
|
||||||
|
|
|
@ -4,8 +4,9 @@ namespace Misuzu;
|
||||||
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_USERS_MANAGE))
|
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_USERS_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$usersCtx = $msz->getUsersContext();
|
||||||
$roles = $msz->getRoles();
|
$users = $usersCtx->getUsers();
|
||||||
|
$roles = $usersCtx->getRoles();
|
||||||
$pagination = new Pagination($users->countUsers(), 30);
|
$pagination = new Pagination($users->countUsers(), 30);
|
||||||
|
|
||||||
if(!$pagination->hasValidOffset())
|
if(!$pagination->hasValidOffset())
|
||||||
|
|
|
@ -3,7 +3,8 @@ namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_NOTES_MANAGE))
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!$authInfo->getPerms('user')->check(Perm::U_NOTES_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$hasNoteId = filter_has_var(INPUT_GET, 'n');
|
$hasNoteId = filter_has_var(INPUT_GET, 'n');
|
||||||
|
@ -12,19 +13,20 @@ $hasUserId = filter_has_var(INPUT_GET, 'u');
|
||||||
if((!$hasNoteId && !$hasUserId) || ($hasNoteId && $hasUserId))
|
if((!$hasNoteId && !$hasUserId) || ($hasNoteId && $hasUserId))
|
||||||
Template::throwError(400);
|
Template::throwError(400);
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$urls = $msz->getURLs();
|
||||||
$modNotes = $msz->getModNotes();
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$modNotes = $usersCtx->getModNotes();
|
||||||
|
|
||||||
if($hasUserId) {
|
if($hasUserId) {
|
||||||
$isNew = true;
|
$isNew = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$userInfo = $users->getUser(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id');
|
$userInfo = $usersCtx->getUserInfo(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT));
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$authorInfo = $msz->getActiveUser();
|
$authorInfo = $authInfo->getUserInfo();
|
||||||
} elseif($hasNoteId) {
|
} elseif($hasNoteId) {
|
||||||
$isNew = false;
|
$isNew = false;
|
||||||
|
|
||||||
|
@ -40,12 +42,12 @@ if($hasUserId) {
|
||||||
|
|
||||||
$modNotes->deleteNotes($noteInfo);
|
$modNotes->deleteNotes($noteInfo);
|
||||||
$msz->createAuditLog('MOD_NOTE_DELETE', [$noteInfo->getId(), $noteInfo->getUserId()]);
|
$msz->createAuditLog('MOD_NOTE_DELETE', [$noteInfo->getId(), $noteInfo->getUserId()]);
|
||||||
url_redirect('manage-users-notes', ['user' => $noteInfo->getUserId()]);
|
Tools::redirect($urls->format('manage-users-notes', ['user' => $noteInfo->getUserId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$userInfo = $users->getUser($noteInfo->getUserId(), 'id');
|
$userInfo = $usersCtx->getUserInfo($noteInfo->getUserId());
|
||||||
$authorInfo = $noteInfo->hasAuthorId() ? $users->getUser($noteInfo->getAuthorId(), 'id') : null;
|
$authorInfo = $noteInfo->hasAuthorId() ? $usersCtx->getUserInfo($noteInfo->getAuthorId()) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
|
@ -70,7 +72,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// this is easier
|
// this is easier
|
||||||
url_redirect('manage-users-note', ['note' => $noteInfo->getId()]);
|
Tools::redirect($urls->format('manage-users-note', ['note' => $noteInfo->getId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +80,7 @@ Template::render('manage.users.note', [
|
||||||
'note_new' => $isNew,
|
'note_new' => $isNew,
|
||||||
'note_info' => $noteInfo ?? null,
|
'note_info' => $noteInfo ?? null,
|
||||||
'note_user' => $userInfo,
|
'note_user' => $userInfo,
|
||||||
'note_user_colour' => $users->getUserColour($userInfo),
|
'note_user_colour' => $usersCtx->getUserColour($userInfo),
|
||||||
'note_author' => $authorInfo,
|
'note_author' => $authorInfo,
|
||||||
'note_author_colour' => $users->getUserColour($authorInfo),
|
'note_author_colour' => $usersCtx->getUserColour($authorInfo),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -6,28 +6,19 @@ use RuntimeException;
|
||||||
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_NOTES_MANAGE))
|
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_NOTES_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$modNotes = $usersCtx->getModNotes();
|
||||||
$userInfos = [
|
|
||||||
$msz->getActiveUser()->getId() => $msz->getActiveUser(),
|
|
||||||
];
|
|
||||||
$userColours = [
|
|
||||||
$msz->getActiveUser()->getId() => $users->getUserColour($msz->getActiveUser()),
|
|
||||||
];
|
|
||||||
|
|
||||||
$filterUser = null;
|
$filterUser = null;
|
||||||
if(filter_has_var(INPUT_GET, 'u')) {
|
if(filter_has_var(INPUT_GET, 'u')) {
|
||||||
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
|
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
|
||||||
try {
|
try {
|
||||||
$filterUser = $users->getUser($filterUserId, 'id');
|
$filterUser = $usersCtx->getUserInfo($filterUserId);
|
||||||
$userInfos[$filterUserId] = $filterUser;
|
|
||||||
$userColours[$filterUserId] = $users->getUserColour($filterUser);
|
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$modNotes = $msz->getModNotes();
|
|
||||||
$pagination = new Pagination($modNotes->countNotes(userInfo: $filterUser), 10);
|
$pagination = new Pagination($modNotes->countNotes(userInfo: $filterUser), 10);
|
||||||
|
|
||||||
if(!$pagination->hasValidOffset())
|
if(!$pagination->hasValidOffset())
|
||||||
|
@ -37,29 +28,15 @@ $notes = [];
|
||||||
$noteInfos = $modNotes->getNotes(userInfo: $filterUser, pagination: $pagination);
|
$noteInfos = $modNotes->getNotes(userInfo: $filterUser, pagination: $pagination);
|
||||||
|
|
||||||
foreach($noteInfos as $noteInfo) {
|
foreach($noteInfos as $noteInfo) {
|
||||||
if(array_key_exists($noteInfo->getUserId(), $userInfos))
|
$userInfo = $usersCtx->getUserInfo($noteInfo->getUserId());
|
||||||
$userInfo = $userInfos[$noteInfo->getUserId()];
|
$userColour = $usersCtx->getUserColour($userInfo);
|
||||||
else
|
|
||||||
$userInfos[$noteInfo->getUserId()] = $userInfo = $users->getUser($noteInfo->getUserId(), 'id');
|
|
||||||
|
|
||||||
if(array_key_exists($userInfo->getId(), $userColours))
|
|
||||||
$userColour = $userColours[$userInfo->getId()];
|
|
||||||
else
|
|
||||||
$userColours[$userInfo->getId()] = $userColour = $users->getUserColour($userInfo);
|
|
||||||
|
|
||||||
if(!$noteInfo->hasAuthorId()) {
|
if(!$noteInfo->hasAuthorId()) {
|
||||||
$authorInfo = null;
|
$authorInfo = null;
|
||||||
$authorColour = null;
|
$authorColour = null;
|
||||||
} else {
|
} else {
|
||||||
if(array_key_exists($noteInfo->getAuthorId(), $userInfos))
|
$authorInfo = $usersCtx->getUserInfo($noteInfo->getAuthorId());
|
||||||
$authorInfo = $userInfos[$noteInfo->getAuthorId()];
|
$authorColour = $usersCtx->getUserColour($authorInfo);
|
||||||
else
|
|
||||||
$userInfos[$noteInfo->getAuthorId()] = $modInfo = $users->getUser($noteInfo->getAuthorId(), 'id');
|
|
||||||
|
|
||||||
if(array_key_exists($authorInfo->getId(), $userColours))
|
|
||||||
$authorColour = $userColours[$authorInfo->getId()];
|
|
||||||
else
|
|
||||||
$userColours[$authorInfo->getId()] = $authorColour = $users->getUserColour($authorInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$notes[] = [
|
$notes[] = [
|
||||||
|
|
|
@ -6,12 +6,15 @@ use Index\Colour\Colour;
|
||||||
use Index\Colour\ColourRGB;
|
use Index\Colour\ColourRGB;
|
||||||
use Misuzu\Perm;
|
use Misuzu\Perm;
|
||||||
|
|
||||||
$viewerPerms = $msz->getAuthInfo()->getPerms('user');
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
$viewerPerms = $authInfo->getPerms('user');
|
||||||
if(!$viewerPerms->check(Perm::U_ROLES_MANAGE))
|
if(!$viewerPerms->check(Perm::U_ROLES_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$roleInfo = null;
|
||||||
$roles = $msz->getRoles();
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$users = $usersCtx->getUsers();
|
||||||
|
$roles = $usersCtx->getRoles();
|
||||||
$perms = $msz->getPerms();
|
$perms = $msz->getPerms();
|
||||||
|
|
||||||
if(filter_has_var(INPUT_GET, 'r')) {
|
if(filter_has_var(INPUT_GET, 'r')) {
|
||||||
|
@ -25,10 +28,10 @@ if(filter_has_var(INPUT_GET, 'r')) {
|
||||||
}
|
}
|
||||||
} else $isNew = true;
|
} else $isNew = true;
|
||||||
|
|
||||||
$currentUser = $msz->getActiveUser();
|
$currentUser = $authInfo->getUserInfo();
|
||||||
$canEditPerms = $viewerPerms->check(Perm::U_PERMS_MANAGE);
|
$canEditPerms = $viewerPerms->check(Perm::U_PERMS_MANAGE);
|
||||||
|
|
||||||
$permsInfos = $perms->getPermissionInfo(roleInfo: $roleInfo, categoryNames: Perm::INFO_FOR_ROLE);
|
$permsInfos = $roleInfo === null ? null : $perms->getPermissionInfo(roleInfo: $roleInfo, categoryNames: Perm::INFO_FOR_ROLE);
|
||||||
$permsLists = Perm::createList(Perm::LISTS_FOR_ROLE);
|
$permsLists = Perm::createList(Perm::LISTS_FOR_ROLE);
|
||||||
|
|
||||||
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
|
@ -133,7 +136,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$msz->getConfig()->setBoolean('perms.needsRecalc', true);
|
$msz->getConfig()->setBoolean('perms.needsRecalc', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
url_redirect('manage-role', ['role' => $roleInfo->getId()]);
|
Tools::redirect($msz->getURLs()->format('manage-role', ['role' => $roleInfo->getId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace Misuzu;
|
||||||
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_ROLES_MANAGE))
|
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_ROLES_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$roles = $msz->getRoles();
|
$roles = $msz->getUsersContext()->getRoles();
|
||||||
$pagination = new Pagination($roles->countRoles(), 10);
|
$pagination = new Pagination($roles->countRoles(), 10);
|
||||||
|
|
||||||
if(!$pagination->hasValidOffset())
|
if(!$pagination->hasValidOffset())
|
||||||
|
|
|
@ -7,15 +7,18 @@ use Misuzu\Perm;
|
||||||
use Misuzu\Auth\AuthTokenCookie;
|
use Misuzu\Auth\AuthTokenCookie;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
|
||||||
$viewerPerms = $msz->getAuthInfo()->getPerms('user');
|
$authInfo = $msz->getAuthInfo();
|
||||||
if(!$msz->isLoggedIn())
|
$viewerPerms = $authInfo->getPerms('user');
|
||||||
|
if(!$authInfo->isLoggedIn())
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$urls = $msz->getURLs();
|
||||||
$roles = $msz->getRoles();
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$users = $usersCtx->getUsers();
|
||||||
|
$roles = $usersCtx->getRoles();
|
||||||
$perms = $msz->getPerms();
|
$perms = $msz->getPerms();
|
||||||
|
|
||||||
$currentUser = $msz->getActiveUser();
|
$currentUser = $authInfo->getUserInfo();
|
||||||
|
|
||||||
$canManageUsers = $viewerPerms->check(Perm::U_USERS_MANAGE);
|
$canManageUsers = $viewerPerms->check(Perm::U_USERS_MANAGE);
|
||||||
$canManagePerms = $viewerPerms->check(Perm::U_PERMS_MANAGE);
|
$canManagePerms = $viewerPerms->check(Perm::U_PERMS_MANAGE);
|
||||||
|
@ -65,12 +68,12 @@ if(CSRF::validateRequest() && $canEdit) {
|
||||||
if($allowToImpersonate) {
|
if($allowToImpersonate) {
|
||||||
$msz->createAuditLog('USER_IMPERSONATE', [$userInfo->getId(), $userInfo->getName()]);
|
$msz->createAuditLog('USER_IMPERSONATE', [$userInfo->getId(), $userInfo->getName()]);
|
||||||
|
|
||||||
$tokenBuilder = $msz->getAuthInfo()->getTokenInfo()->toBuilder();
|
$tokenBuilder = $authInfo->getTokenInfo()->toBuilder();
|
||||||
$tokenBuilder->setImpersonatedUserId($userInfo->getId());
|
$tokenBuilder->setImpersonatedUserId($userInfo->getId());
|
||||||
$tokenInfo = $tokenBuilder->toInfo();
|
$tokenInfo = $tokenBuilder->toInfo();
|
||||||
|
|
||||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||||
url_redirect('index');
|
Tools::redirect($urls->format('index'));
|
||||||
return;
|
return;
|
||||||
} else $notices[] = 'You aren\'t allowed to impersonate this user.';
|
} else $notices[] = 'You aren\'t allowed to impersonate this user.';
|
||||||
}
|
}
|
||||||
|
@ -103,7 +106,7 @@ if(CSRF::validateRequest() && $canEdit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$existingRoles = [];
|
$existingRoles = [];
|
||||||
foreach($roles->getRoles(userInfo: $userInfo) as $roleInfo)
|
foreach(iterator_to_array($roles->getRoles(userInfo: $userInfo)) as $roleInfo)
|
||||||
$existingRoles[$roleInfo->getId()] = $roleInfo;
|
$existingRoles[$roleInfo->getId()] = $roleInfo;
|
||||||
|
|
||||||
$removeRoles = [];
|
$removeRoles = [];
|
||||||
|
@ -214,13 +217,16 @@ if(CSRF::validateRequest() && $canEdit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if($permsNeedRecalc)
|
if($permsNeedRecalc)
|
||||||
$perms->precalculatePermissions($msz->getForum(), [$userInfo->getId()]);
|
$perms->precalculatePermissions(
|
||||||
|
$msz->getForumContext()->getCategories(),
|
||||||
|
[$userInfo->getId()]
|
||||||
|
);
|
||||||
|
|
||||||
url_redirect('manage-user', ['user' => $userInfo->getId()]);
|
Tools::redirect($urls->format('manage-user', ['user' => $userInfo->getId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$rolesAll = $roles->getRoles();
|
$rolesAll = iterator_to_array($roles->getRoles());
|
||||||
$userRoleIds = $users->hasRoles($userInfo, $rolesAll);
|
$userRoleIds = $users->hasRoles($userInfo, $rolesAll);
|
||||||
|
|
||||||
Template::render('manage.users.user', [
|
Template::render('manage.users.user', [
|
||||||
|
|
|
@ -3,10 +3,14 @@ namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_WARNINGS_MANAGE))
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!$authInfo->getPerms('user')->check(Perm::U_WARNINGS_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$warns = $msz->getWarnings();
|
$urls = $msz->getURLs();
|
||||||
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$users = $usersCtx->getUsers();
|
||||||
|
$warns = $usersCtx->getWarnings();
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) {
|
if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) {
|
||||||
if(!CSRF::validateRequest())
|
if(!CSRF::validateRequest())
|
||||||
|
@ -20,19 +24,17 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete'))
|
||||||
|
|
||||||
$warns->deleteWarnings($warnInfo);
|
$warns->deleteWarnings($warnInfo);
|
||||||
$msz->createAuditLog('WARN_DELETE', [$warnInfo->getId(), $warnInfo->getUserId()]);
|
$msz->createAuditLog('WARN_DELETE', [$warnInfo->getId(), $warnInfo->getUserId()]);
|
||||||
url_redirect('manage-users-warnings', ['user' => $warnInfo->getUserId()]);
|
Tools::redirect($urls->format('manage-users-warnings', ['user' => $warnInfo->getUserId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$userInfo = $users->getUser(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id');
|
$userInfo = $users->getUser(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id');
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$modInfo = $msz->getActiveUser();
|
$modInfo = $authInfo->getUserInfo();
|
||||||
|
|
||||||
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$body = trim((string)filter_input(INPUT_POST, 'uw_body'));
|
$body = trim((string)filter_input(INPUT_POST, 'uw_body'));
|
||||||
|
@ -43,7 +45,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
);
|
);
|
||||||
|
|
||||||
$msz->createAuditLog('WARN_CREATE', [$warnInfo->getId(), $userInfo->getId()]);
|
$msz->createAuditLog('WARN_CREATE', [$warnInfo->getId(), $userInfo->getId()]);
|
||||||
url_redirect('manage-users-warnings', ['user' => $userInfo->getId()]);
|
Tools::redirect($urls->format('manage-users-warnings', ['user' => $userInfo->getId()]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,28 +6,19 @@ use RuntimeException;
|
||||||
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_WARNINGS_MANAGE))
|
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_WARNINGS_MANAGE))
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$warns = $usersCtx->getWarnings();
|
||||||
$userInfos = [
|
|
||||||
$msz->getActiveUser()->getId() => $msz->getActiveUser(),
|
|
||||||
];
|
|
||||||
$userColours = [
|
|
||||||
$msz->getActiveUser()->getId() => $users->getUserColour($msz->getActiveUser()),
|
|
||||||
];
|
|
||||||
|
|
||||||
$filterUser = null;
|
$filterUser = null;
|
||||||
if(filter_has_var(INPUT_GET, 'u')) {
|
if(filter_has_var(INPUT_GET, 'u')) {
|
||||||
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
|
$filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
|
||||||
try {
|
try {
|
||||||
$filterUser = $users->getUser($filterUserId, 'id');
|
$filterUser = $usersCtx->getUserInfo($filterUserId);
|
||||||
$userInfos[$filterUserId] = $filterUser;
|
|
||||||
$userColours[$filterUserId] = $users->getUserColour($filterUser);
|
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$warns = $msz->getWarnings();
|
|
||||||
$pagination = new Pagination($warns->countWarnings(userInfo: $filterUser), 10);
|
$pagination = new Pagination($warns->countWarnings(userInfo: $filterUser), 10);
|
||||||
|
|
||||||
if(!$pagination->hasValidOffset())
|
if(!$pagination->hasValidOffset())
|
||||||
|
@ -37,29 +28,15 @@ $warnList = [];
|
||||||
$warnInfos = $warns->getWarnings(userInfo: $filterUser, pagination: $pagination);
|
$warnInfos = $warns->getWarnings(userInfo: $filterUser, pagination: $pagination);
|
||||||
|
|
||||||
foreach($warnInfos as $warnInfo) {
|
foreach($warnInfos as $warnInfo) {
|
||||||
if(array_key_exists($warnInfo->getUserId(), $userInfos))
|
$userInfo = $usersCtx->getUserInfo($warnInfo->getUserId());
|
||||||
$userInfo = $userInfos[$warnInfo->getUserId()];
|
$userColour = $usersCtx->getUserColour($userInfo);
|
||||||
else
|
|
||||||
$userInfos[$warnInfo->getUserId()] = $userInfo = $users->getUser($warnInfo->getUserId(), 'id');
|
|
||||||
|
|
||||||
if(array_key_exists($userInfo->getId(), $userColours))
|
|
||||||
$userColour = $userColours[$userInfo->getId()];
|
|
||||||
else
|
|
||||||
$userColours[$userInfo->getId()] = $userColour = $users->getUserColour($userInfo);
|
|
||||||
|
|
||||||
if(!$warnInfo->hasModId()) {
|
if(!$warnInfo->hasModId()) {
|
||||||
$modInfo = null;
|
$modInfo = null;
|
||||||
$modColour = null;
|
$modColour = null;
|
||||||
} else {
|
} else {
|
||||||
if(array_key_exists($warnInfo->getModId(), $userInfos))
|
$modInfo = $usersCtx->getUserInfo($warnInfo->getModId());
|
||||||
$modInfo = $userInfos[$warnInfo->getModId()];
|
$modColour = $usersCtx->getUserColour($modInfo);
|
||||||
else
|
|
||||||
$userInfos[$warnInfo->getModId()] = $modInfo = $users->getUser($warnInfo->getModId(), 'id');
|
|
||||||
|
|
||||||
if(array_key_exists($modInfo->getId(), $userColours))
|
|
||||||
$modColour = $userColours[$modInfo->getId()];
|
|
||||||
else
|
|
||||||
$userColours[$modInfo->getId()] = $modColour = $users->getUserColour($modInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$warnList[] = [
|
$warnList[] = [
|
||||||
|
|
|
@ -3,14 +3,16 @@ namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
if(!$msz->isLoggedIn())
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!$authInfo->isLoggedIn())
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
// TODO: restore forum-topics and forum-posts orderings
|
// TODO: restore forum-topics and forum-posts orderings
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$forumCtx = $msz->getForumContext();
|
||||||
$roles = $msz->getRoles();
|
$usersCtx = $msz->getUsersContext();
|
||||||
$forum = $msz->getForum();
|
$users = $usersCtx->getUsers();
|
||||||
|
$roles = $usersCtx->getRoles();
|
||||||
|
|
||||||
$roleId = filter_has_var(INPUT_GET, 'r') ? (string)filter_input(INPUT_GET, 'r') : null;
|
$roleId = filter_has_var(INPUT_GET, 'r') ? (string)filter_input(INPUT_GET, 'r') : null;
|
||||||
$orderBy = strtolower((string)filter_input(INPUT_GET, 'ss'));
|
$orderBy = strtolower((string)filter_input(INPUT_GET, 'ss'));
|
||||||
|
@ -73,7 +75,7 @@ if($roleId === null) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$canManageUsers = $msz->getAuthInfo()->getPerms('user')->check(Perm::U_USERS_MANAGE);
|
$canManageUsers = $authInfo->getPerms('user')->check(Perm::U_USERS_MANAGE);
|
||||||
$deleted = $canManageUsers ? null : false;
|
$deleted = $canManageUsers ? null : false;
|
||||||
|
|
||||||
$rolesAll = $roles->getRoles(hidden: false);
|
$rolesAll = $roles->getRoles(hidden: false);
|
||||||
|
@ -91,9 +93,9 @@ $userInfos = $users->getUsers(
|
||||||
foreach($userInfos as $userInfo)
|
foreach($userInfos as $userInfo)
|
||||||
$userList[] = [
|
$userList[] = [
|
||||||
'info' => $userInfo,
|
'info' => $userInfo,
|
||||||
'colour' => $users->getUserColour($userInfo),
|
'colour' => $usersCtx->getUserColour($userInfo),
|
||||||
'ftopics' => $forum->countTopics(userInfo: $userInfo, deleted: false),
|
'ftopics' => $forumCtx->countTotalUserTopics($userInfo),
|
||||||
'fposts' => $forum->countPosts(userInfo: $userInfo, deleted: false),
|
'fposts' => $forumCtx->countTotalUserPosts($userInfo),
|
||||||
];
|
];
|
||||||
|
|
||||||
if(empty($userList))
|
if(empty($userList))
|
||||||
|
|
|
@ -15,15 +15,21 @@ $userId = !empty($_GET['u']) && is_string($_GET['u']) ? trim($_GET['u']) : 0;
|
||||||
$profileMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
$profileMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
||||||
$isEditing = !empty($_GET['edit']) && is_string($_GET['edit']) ? (bool)$_GET['edit'] : !empty($_POST) && is_array($_POST);
|
$isEditing = !empty($_GET['edit']) && is_string($_GET['edit']) ? (bool)$_GET['edit'] : !empty($_POST) && is_array($_POST);
|
||||||
|
|
||||||
$users = $msz->getUsers();
|
$urls = $msz->getURLs();
|
||||||
$forum = $msz->getForum();
|
$usersCtx = $msz->getUsersContext();
|
||||||
|
$users = $usersCtx->getUsers();
|
||||||
|
$forumCtx = $msz->getForumContext();
|
||||||
|
$forumCategories = $forumCtx->getCategories();
|
||||||
|
$forumTopics = $forumCtx->getTopics();
|
||||||
|
$forumPosts = $forumCtx->getPosts();
|
||||||
|
|
||||||
$viewerInfo = $msz->getActiveUser();
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
$viewerInfo = $authInfo->getUserInfo();
|
||||||
$viewingAsGuest = $viewerInfo === null;
|
$viewingAsGuest = $viewerInfo === null;
|
||||||
$viewerId = $viewingAsGuest ? '0' : $viewerInfo->getId();
|
$viewerId = $viewingAsGuest ? '0' : $viewerInfo->getId();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$userInfo = $users->getUser($userId, 'profile');
|
$userInfo = $usersCtx->getUserInfo($userId, 'profile');
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
Template::render('profile.index', [
|
Template::render('profile.index', [
|
||||||
|
@ -49,11 +55,11 @@ switch($profileMode) {
|
||||||
Template::throwError(404);
|
Template::throwError(404);
|
||||||
|
|
||||||
case 'forum-topics':
|
case 'forum-topics':
|
||||||
url_redirect('search-query', ['query' => sprintf('type:forum:topic author:%s', $userInfo->getName()), 'section' => 'topics']);
|
Tools::redirect($urls->format('search-query', ['query' => sprintf('type:forum:topic author:%s', $userInfo->getName()), 'section' => 'topics']));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'forum-posts':
|
case 'forum-posts':
|
||||||
url_redirect('search-query', ['query' => sprintf('type:forum:post author:%s', $userInfo->getName()), 'section' => 'posts']);
|
Tools::redirect($urls->format('search-query', ['query' => sprintf('type:forum:post author:%s', $userInfo->getName()), 'section' => 'posts']));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case '':
|
case '':
|
||||||
|
@ -62,18 +68,19 @@ switch($profileMode) {
|
||||||
|
|
||||||
$notices = [];
|
$notices = [];
|
||||||
|
|
||||||
$userRank = $users->getUserRank($userInfo);
|
$userRank = $usersCtx->getUserRank($userInfo);
|
||||||
$viewerRank = $viewingAsGuest ? 0 : $users->getUserRank($viewerInfo);
|
$viewerRank = $usersCtx->getUserRank($viewerInfo);
|
||||||
|
|
||||||
$viewerPerms = $msz->getAuthInfo()->getPerms('user');
|
$viewerPermsGlobal = $authInfo->getPerms('global');
|
||||||
|
$viewerPermsUser = $authInfo->getPerms('user');
|
||||||
|
|
||||||
$activeBanInfo = $msz->tryGetActiveBan($userInfo);
|
$activeBanInfo = $usersCtx->tryGetActiveBan($userInfo);
|
||||||
$isBanned = $activeBanInfo !== null;
|
$isBanned = $activeBanInfo !== null;
|
||||||
$profileFields = $msz->getProfileFields();
|
$profileFields = $msz->getProfileFields();
|
||||||
$viewingOwnProfile = (string)$viewerId === $userInfo->getId();
|
$viewingOwnProfile = (string)$viewerId === $userInfo->getId();
|
||||||
$canManageWarnings = $viewerPerms->check(Perm::U_WARNINGS_MANAGE);
|
$canManageWarnings = $viewerPermsUser->check(Perm::U_WARNINGS_MANAGE);
|
||||||
$canEdit = !$viewingAsGuest && ((!$isBanned && $viewingOwnProfile) || $viewerInfo->isSuperUser() || (
|
$canEdit = !$viewingAsGuest && ((!$isBanned && $viewingOwnProfile) || $viewerInfo->isSuperUser() || (
|
||||||
$viewerPerms->check(Perm::U_USERS_MANAGE) && ($viewingOwnProfile || $viewerRank > $userRank)
|
$viewerPermsUser->check(Perm::U_USERS_MANAGE) && ($viewingOwnProfile || $viewerRank > $userRank)
|
||||||
));
|
));
|
||||||
$avatarInfo = new UserAvatarAsset($userInfo);
|
$avatarInfo = new UserAvatarAsset($userInfo);
|
||||||
$backgroundInfo = new UserBackgroundAsset($userInfo);
|
$backgroundInfo = new UserBackgroundAsset($userInfo);
|
||||||
|
@ -82,7 +89,7 @@ if($isEditing) {
|
||||||
if(!$canEdit)
|
if(!$canEdit)
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$perms = $viewerPerms->checkMany([
|
$perms = $viewerPermsUser->checkMany([
|
||||||
'edit_profile' => Perm::U_PROFILE_EDIT,
|
'edit_profile' => Perm::U_PROFILE_EDIT,
|
||||||
'edit_avatar' => Perm::U_AVATAR_CHANGE,
|
'edit_avatar' => Perm::U_AVATAR_CHANGE,
|
||||||
'edit_background' => PERM::U_PROFILE_BACKGROUND_CHANGE,
|
'edit_background' => PERM::U_PROFILE_BACKGROUND_CHANGE,
|
||||||
|
@ -106,7 +113,7 @@ if($isEditing) {
|
||||||
if(!$perms->edit_profile) {
|
if(!$perms->edit_profile) {
|
||||||
$notices[] = 'You\'re not allowed to edit your profile';
|
$notices[] = 'You\'re not allowed to edit your profile';
|
||||||
} else {
|
} else {
|
||||||
$profileFieldInfos = $profileFields->getFields();
|
$profileFieldInfos = iterator_to_array($profileFields->getFields());
|
||||||
$profileFieldsSetInfos = [];
|
$profileFieldsSetInfos = [];
|
||||||
$profileFieldsSetValues = [];
|
$profileFieldsSetValues = [];
|
||||||
$profileFieldsRemove = [];
|
$profileFieldsRemove = [];
|
||||||
|
@ -287,12 +294,12 @@ if($isEditing) {
|
||||||
|
|
||||||
// TODO: create user counters so these can be statically kept
|
// TODO: create user counters so these can be statically kept
|
||||||
$profileStats = new stdClass;
|
$profileStats = new stdClass;
|
||||||
$profileStats->forum_topic_count = $forum->countTopics(userInfo: $userInfo, deleted: false);
|
$profileStats->forum_topic_count = $forumCtx->countTotalUserTopics($userInfo);
|
||||||
$profileStats->forum_post_count = $forum->countPosts(userInfo: $userInfo, deleted: false);
|
$profileStats->forum_post_count = $forumCtx->countTotalUserPosts($userInfo);
|
||||||
$profileStats->comments_count = $msz->getComments()->countPosts(userInfo: $userInfo, deleted: false);
|
$profileStats->comments_count = $msz->getComments()->countPosts(userInfo: $userInfo, deleted: false);
|
||||||
|
|
||||||
if(!$viewingAsGuest) {
|
if(!$viewingAsGuest) {
|
||||||
Template::set('profile_warnings', $msz->getWarnings()->getWarningsWithDefaultBacklog($userInfo));
|
Template::set('profile_warnings', iterator_to_array($usersCtx->getWarnings()->getWarningsWithDefaultBacklog($userInfo)));
|
||||||
|
|
||||||
if((!$isBanned || $canEdit)) {
|
if((!$isBanned || $canEdit)) {
|
||||||
$unranked = $cfg->getValues([
|
$unranked = $cfg->getValues([
|
||||||
|
@ -300,25 +307,25 @@ if(!$viewingAsGuest) {
|
||||||
'forum_leader.unranked.topic:a',
|
'forum_leader.unranked.topic:a',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$activeCategoryStats = $forum->getMostActiveCategoryInfo(
|
$activeCategoryStats = $forumCategories->getMostActiveCategoryInfo(
|
||||||
$userInfo,
|
$userInfo,
|
||||||
$unranked['forum_leader.unranked.forum'],
|
$unranked['forum_leader.unranked.forum'],
|
||||||
$unranked['forum_leader.unranked.topic'],
|
$unranked['forum_leader.unranked.topic'],
|
||||||
deleted: false
|
deleted: false
|
||||||
);
|
);
|
||||||
$activeCategoryInfo = $activeCategoryStats->success ? $forum->getCategory(categoryId: $activeCategoryStats->categoryId) : null;
|
$activeCategoryInfo = $activeCategoryStats->success ? $forumCategories->getCategory(categoryId: $activeCategoryStats->categoryId) : null;
|
||||||
|
|
||||||
$activeTopicStats = $forum->getMostActiveTopicInfo(
|
$activeTopicStats = $forumTopics->getMostActiveTopicInfo(
|
||||||
$userInfo,
|
$userInfo,
|
||||||
$unranked['forum_leader.unranked.forum'],
|
$unranked['forum_leader.unranked.forum'],
|
||||||
$unranked['forum_leader.unranked.topic'],
|
$unranked['forum_leader.unranked.topic'],
|
||||||
deleted: false
|
deleted: false
|
||||||
);
|
);
|
||||||
$activeTopicInfo = $activeTopicStats->success ? $forum->getTopic(topicId: $activeTopicStats->topicId) : null;
|
$activeTopicInfo = $activeTopicStats->success ? $forumTopics->getTopic(topicId: $activeTopicStats->topicId) : null;
|
||||||
|
|
||||||
$profileFieldValues = $profileFields->getFieldValues($userInfo);
|
$profileFieldValues = iterator_to_array($profileFields->getFieldValues($userInfo));
|
||||||
$profileFieldInfos = $profileFieldInfos ?? $profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues);
|
$profileFieldInfos = $profileFieldInfos ?? iterator_to_array($profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues));
|
||||||
$profileFieldFormats = $profileFields->getFieldFormats(fieldValueInfos: $profileFieldValues);
|
$profileFieldFormats = iterator_to_array($profileFields->getFieldFormats(fieldValueInfos: $profileFieldValues));
|
||||||
|
|
||||||
$profileFieldRawValues = [];
|
$profileFieldRawValues = [];
|
||||||
$profileFieldLinkValues = [];
|
$profileFieldLinkValues = [];
|
||||||
|
@ -366,7 +373,7 @@ if(!$viewingAsGuest) {
|
||||||
Template::render('profile.index', [
|
Template::render('profile.index', [
|
||||||
'profile_viewer' => $viewerInfo,
|
'profile_viewer' => $viewerInfo,
|
||||||
'profile_user' => $userInfo,
|
'profile_user' => $userInfo,
|
||||||
'profile_colour' => $users->getUserColour($userInfo),
|
'profile_colour' => $usersCtx->getUserColour($userInfo),
|
||||||
'profile_stats' => $profileStats,
|
'profile_stats' => $profileStats,
|
||||||
'profile_mode' => $profileMode,
|
'profile_mode' => $profileMode,
|
||||||
'profile_notices' => $notices,
|
'profile_notices' => $notices,
|
||||||
|
@ -378,4 +385,5 @@ Template::render('profile.index', [
|
||||||
'profile_ban_info' => $activeBanInfo,
|
'profile_ban_info' => $activeBanInfo,
|
||||||
'profile_avatar_info' => $avatarInfo,
|
'profile_avatar_info' => $avatarInfo,
|
||||||
'profile_background_info' => $backgroundInfo,
|
'profile_background_info' => $backgroundInfo,
|
||||||
|
'profile_can_send_messages' => $viewerPermsGlobal->check(Perm::G_MESSAGES_SEND),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -6,7 +6,8 @@ use RuntimeException;
|
||||||
use Index\XArray;
|
use Index\XArray;
|
||||||
use Misuzu\Comments\CommentsCategory;
|
use Misuzu\Comments\CommentsCategory;
|
||||||
|
|
||||||
if(!$msz->isLoggedIn())
|
$authInfo = $msz->getAuthInfo();
|
||||||
|
if(!$authInfo->isLoggedIn())
|
||||||
Template::throwError(403);
|
Template::throwError(403);
|
||||||
|
|
||||||
$searchQuery = !empty($_GET['q']) && is_string($_GET['q']) ? $_GET['q'] : '';
|
$searchQuery = !empty($_GET['q']) && is_string($_GET['q']) ? $_GET['q'] : '';
|
||||||
|
@ -35,17 +36,18 @@ Template::addFunction('search_merge_query', function($attrs) use (&$searchQueryE
|
||||||
empty($attrs['query']) ? [] : explode(' ', $attrs['query'])
|
empty($attrs['query']) ? [] : explode(' ', $attrs['query'])
|
||||||
)));
|
)));
|
||||||
|
|
||||||
return rawurlencode(implode(' ', $existing));
|
return implode(' ', $existing);
|
||||||
});
|
});
|
||||||
if(!empty($searchQuery)) {
|
if(!empty($searchQuery)) {
|
||||||
$users = $msz->getUsers();
|
$usersCtx = $msz->getUsersContext();
|
||||||
$forum = $msz->getForum();
|
$users = $usersCtx->getUsers();
|
||||||
|
$forumCtx = $msz->getForumContext();
|
||||||
|
$forumCategories = $forumCtx->getCategories();
|
||||||
|
$forumTopics = $forumCtx->getTopics();
|
||||||
|
$forumPosts = $forumCtx->getPosts();
|
||||||
$news = $msz->getNews();
|
$news = $msz->getNews();
|
||||||
$comments = $msz->getComments();
|
$comments = $msz->getComments();
|
||||||
|
|
||||||
$userInfos = [];
|
|
||||||
$userColours = [];
|
|
||||||
|
|
||||||
$searchQueryAttributes = ['type', 'author', 'after'];
|
$searchQueryAttributes = ['type', 'author', 'after'];
|
||||||
$searchQueryParts = explode(' ', $searchQuery);
|
$searchQueryParts = explode(' ', $searchQuery);
|
||||||
foreach($searchQueryParts as $queryPart) {
|
foreach($searchQueryParts as $queryPart) {
|
||||||
|
@ -70,82 +72,60 @@ if(!empty($searchQuery)) {
|
||||||
|
|
||||||
if(!empty($searchQueryEvaluated['author']))
|
if(!empty($searchQueryEvaluated['author']))
|
||||||
try {
|
try {
|
||||||
$searchQueryEvaluated['author'] = $users->getUser($searchQueryEvaluated['author'], 'search');
|
$searchQueryEvaluated['author'] = $usersCtx->getUserInfo($searchQueryEvaluated['author'], 'search');
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
unset($searchQueryEvaluated['author']);
|
unset($searchQueryEvaluated['author']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(empty($searchQueryEvaluated['type']) || str_starts_with($searchQueryEvaluated['type'], 'forum')) {
|
if(empty($searchQueryEvaluated['type']) || str_starts_with($searchQueryEvaluated['type'], 'forum')) {
|
||||||
$currentUser = $msz->getActiveUser();
|
$currentUser = $authInfo->getUserInfo();
|
||||||
$currentUserId = $currentUser === null ? 0 : (int)$currentUser->getId();
|
$currentUserId = $currentUser === null ? 0 : (int)$currentUser->getId();
|
||||||
|
|
||||||
$forumCategoryIds = XArray::where(
|
$forumCategoryIds = XArray::where(
|
||||||
$forum->getCategories(hidden: false),
|
$forumCategories->getCategories(hidden: false),
|
||||||
fn($categoryInfo) => $categoryInfo->mayHaveTopics() && $msz->getAuthInfo()->getPerms('forum', $categoryInfo)->check(Perm::F_CATEGORY_VIEW)
|
fn($categoryInfo) => $categoryInfo->mayHaveTopics() && $authInfo->getPerms('forum', $categoryInfo)->check(Perm::F_CATEGORY_VIEW)
|
||||||
);
|
);
|
||||||
|
|
||||||
$forumTopicInfos = $forum->getTopics(categoryInfo: $forumCategoryIds, deleted: false, searchQuery: $searchQueryEvaluated);
|
$forumTopicInfos = $forumTopics->getTopics(categoryInfo: $forumCategoryIds, deleted: false, searchQuery: $searchQueryEvaluated);
|
||||||
$forumTopics = [];
|
$ftopics = [];
|
||||||
|
|
||||||
foreach($forumTopicInfos as $topicInfo) {
|
foreach($forumTopicInfos as $topicInfo) {
|
||||||
$forumTopics[] = $topic = new stdClass;
|
$ftopics[] = $topic = new stdClass;
|
||||||
$topic->info = $topicInfo;
|
$topic->info = $topicInfo;
|
||||||
$topic->unread = $forum->checkTopicUnread($topicInfo, $currentUser);
|
$topic->unread = $forumTopics->checkTopicUnread($topicInfo, $currentUser);
|
||||||
$topic->participated = $forum->checkTopicParticipated($topicInfo, $currentUser);
|
$topic->participated = $forumTopics->checkTopicParticipated($topicInfo, $currentUser);
|
||||||
$topic->lastPost = new stdClass;
|
$topic->lastPost = new stdClass;
|
||||||
|
|
||||||
if($topicInfo->hasUserId()) {
|
if($topicInfo->hasUserId()) {
|
||||||
$lastTopicUserId = $topicInfo->getUserId();
|
$topic->user = $usersCtx->getUserInfo($topicInfo->getUserId(), 'id');
|
||||||
if(!array_key_exists($lastTopicUserId, $userInfos)) {
|
$topic->colour = $usersCtx->getUserColour($topic->user);
|
||||||
$userInfo = $users->getUser($lastTopicUserId, 'id');
|
|
||||||
$userInfos[$lastTopicUserId] = $userInfo;
|
|
||||||
$userColours[$lastTopicUserId] = $users->getUserColour($userInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$topic->user = $userInfos[$lastTopicUserId];
|
|
||||||
$topic->colour = $userColours[$lastTopicUserId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$topic->lastPost->info = $lastPostInfo = $forum->getPost(
|
$topic->lastPost->info = $lastPostInfo = $forumPosts->getPost(
|
||||||
topicInfo: $topicInfo,
|
topicInfo: $topicInfo,
|
||||||
getLast: true,
|
getLast: true,
|
||||||
deleted: $topicInfo->isDeleted() ? null : false,
|
deleted: $topicInfo->isDeleted() ? null : false,
|
||||||
);
|
);
|
||||||
|
|
||||||
if($lastPostInfo->hasUserId()) {
|
if($lastPostInfo->hasUserId()) {
|
||||||
$lastPostUserId = $lastPostInfo->getUserId();
|
$topic->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId(), 'id');
|
||||||
if(!array_key_exists($lastPostUserId, $userInfos)) {
|
$topic->lastPost->colour = $usersCtx->getUserColour($topic->lastPost->user);
|
||||||
$userInfo = $users->getUser($lastPostUserId, 'id');
|
|
||||||
$userInfos[$lastPostUserId] = $userInfo;
|
|
||||||
$userColours[$lastPostUserId] = $users->getUserColour($userInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$topic->lastPost->user = $userInfos[$lastPostUserId];
|
|
||||||
$topic->lastPost->colour = $userColours[$lastPostUserId];
|
|
||||||
}
|
}
|
||||||
} catch(RuntimeException $ex) {}
|
} catch(RuntimeException $ex) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
$forumPostInfos = $forum->getPosts(categoryInfo: $forumCategoryIds, searchQuery: $searchQueryEvaluated);
|
$forumPostInfos = $forumPosts->getPosts(categoryInfo: $forumCategoryIds, searchQuery: $searchQueryEvaluated);
|
||||||
$forumPosts = [];
|
$fposts = [];
|
||||||
|
|
||||||
foreach($forumPostInfos as $postInfo) {
|
foreach($forumPostInfos as $postInfo) {
|
||||||
$forumPosts[] = $post = new stdClass;
|
$fposts[] = $post = new stdClass;
|
||||||
$post->info = $postInfo;
|
$post->info = $postInfo;
|
||||||
|
|
||||||
if($postInfo->hasUserId()) {
|
if($postInfo->hasUserId()) {
|
||||||
$postUserId = $postInfo->getUserId();
|
$post->user = $usersCtx->getUserInfo($postInfo->getUserId(), 'id');
|
||||||
if(!array_key_exists($postUserId, $userInfos)) {
|
$post->colour = $usersCtx->getUserColour($post->user);
|
||||||
$userInfo = $users->getUser($postUserId, 'id');
|
$post->postsCount = $forumCtx->countTotalUserPosts($post->user);
|
||||||
$userInfos[$postUserId] = $userInfo;
|
|
||||||
$userColours[$postUserId] = $users->getUserColour($userInfo);
|
|
||||||
$userPostsCounts[$postUserId] = $forum->countPosts(userInfo: $userInfo, deleted: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
$post->user = $userInfos[$postUserId];
|
|
||||||
$post->colour = $userColours[$postUserId];
|
|
||||||
$post->postsCount = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// can't be bothered sorry
|
// can't be bothered sorry
|
||||||
|
@ -161,19 +141,8 @@ if(!empty($searchQuery)) {
|
||||||
foreach($newsPostInfos as $postInfo) {
|
foreach($newsPostInfos as $postInfo) {
|
||||||
$userId = $postInfo->getUserId();
|
$userId = $postInfo->getUserId();
|
||||||
$categoryId = $postInfo->getCategoryId();
|
$categoryId = $postInfo->getCategoryId();
|
||||||
|
$userInfo = $postInfo->hasUserId() ? $usersCtx->getUserInfo($postInfo->getUserId()) : null;
|
||||||
if(array_key_exists($userId, $userInfos)) {
|
$userColour = $usersCtx->getUserColour($userInfo);
|
||||||
$userInfo = $userInfos[$userId];
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
$userInfo = $users->getUser($userId, 'id');
|
|
||||||
$userColours[$userId] = $users->getUserColour($userInfo);
|
|
||||||
} catch(RuntimeException $ex) {
|
|
||||||
$userInfo = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$userInfos[$userId] = $userInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(array_key_exists($categoryId, $newsCategoryInfos))
|
if(array_key_exists($categoryId, $newsCategoryInfos))
|
||||||
$categoryInfo = $newsCategoryInfos[$categoryId];
|
$categoryInfo = $newsCategoryInfos[$categoryId];
|
||||||
|
@ -187,7 +156,7 @@ if(!empty($searchQuery)) {
|
||||||
'post' => $postInfo,
|
'post' => $postInfo,
|
||||||
'category' => $categoryInfo,
|
'category' => $categoryInfo,
|
||||||
'user' => $userInfo,
|
'user' => $userInfo,
|
||||||
'user_colour' => $userColours[$userId] ?? \Index\Colour\Colour::none(),
|
'user_colour' => $userColour,
|
||||||
'comments_count' => $commentsCount,
|
'comments_count' => $commentsCount,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -198,21 +167,16 @@ if(!empty($searchQuery)) {
|
||||||
foreach($memberInfos as $memberInfo) {
|
foreach($memberInfos as $memberInfo) {
|
||||||
$members[] = $member = new stdClass;
|
$members[] = $member = new stdClass;
|
||||||
$member->info = $memberInfo;
|
$member->info = $memberInfo;
|
||||||
|
$member->colour = $usersCtx->getUserColour($memberInfo);
|
||||||
$memberId = $memberInfo->getId();
|
$member->ftopics = $forumCtx->countTotalUserTopics($memberInfo);
|
||||||
if(!array_key_exists($memberId, $userColours))
|
$member->fposts = $forumCtx->countTotalUserPosts($memberInfo);
|
||||||
$userColours[$memberId] = $users->getUserColour($memberId);
|
|
||||||
|
|
||||||
$member->colour = $userColours[$memberId];
|
|
||||||
$member->ftopics = $forum->countTopics(userInfo: $memberInfo, deleted: false);
|
|
||||||
$member->fposts = $forum->countPosts(userInfo: $memberInfo, deleted: false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Template::render('home.search', [
|
Template::render('home.search', [
|
||||||
'search_query' => $searchQuery,
|
'search_query' => $searchQuery,
|
||||||
'forum_topics' => $forumTopics ?? [],
|
'forum_topics' => $ftopics ?? [],
|
||||||
'forum_posts' => $forumPosts ?? [],
|
'forum_posts' => $fposts ?? [],
|
||||||
'members' => $members ?? [],
|
'members' => $members ?? [],
|
||||||
'news_posts' => $newsPosts ?? [],
|
'news_posts' => $newsPosts ?? [],
|
||||||
]);
|
]);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue