Compare commits
429 Commits
Author | SHA1 | Date | |
---|---|---|---|
e41740db7a | |||
f2dd8cecb4 | |||
330f5c344c | |||
9c26087b46 | |||
37ff4705a6 | |||
46e47f6f39 | |||
29384a4b91 | |||
a6a7aedea2 | |||
1375818d94 | |||
4f68ee6097 | |||
6014609a78 | |||
b1ba30adcb | |||
e93c447758 | |||
a6f71d73c8 | |||
c117e0d2a6 | |||
f93b6cf94b | |||
1b99f17557 | |||
a4f04352a4 | |||
40e774cce9 | |||
9b9ba9c33c | |||
abbaa35188 | |||
68c34a9f93 | |||
f52e97e08a | |||
d45a590531 | |||
a9a5bea1c7 | |||
1ea7c187ab | |||
fc569df34e | |||
f78d09b424 | |||
b97726f6ff | |||
ea5a07b509 | |||
42d814656a | |||
26fcfa1bd9 | |||
aca551038f | |||
2ccb7f3534 | |||
0d9f39d645 | |||
9e29441c68 | |||
c1f8a5e632 | |||
729ed6f6a8 | |||
85f6926300 | |||
8ad607f64f | |||
a2c8ceecc4 | |||
6eac6afbeb | |||
aa20251ee5 | |||
c7f077d89a | |||
e2f6440e18 | |||
f885acb5f2 | |||
eb2b74fdd4 | |||
6543dcc8f9 | |||
6cacb90eef | |||
fe451af7be | |||
65836f4789 | |||
3b5839031e | |||
da9765c352 | |||
d45da1e0ae | |||
a945a1d6db | |||
a243f6b91a | |||
8a2e6948bc | |||
37fdc3057f | |||
0f112adb05 | |||
427f305101 | |||
3a34609fa4 | |||
a3c9338386 | |||
21d72bc17e | |||
09973ae151 | |||
c2e3c8dd23 | |||
d242eedb31 | |||
538165e3ff | |||
2c7f11e57e | |||
e32e569296 | |||
320cd2e194 | |||
894457363e | |||
bf8e6e9e00 | |||
132e9ce3c0 | |||
4ec0df17d6 | |||
4d13a83047 | |||
e382dc7f7d | |||
0ebfaf991a | |||
8b48ba727e | |||
e551a619f1 | |||
34b5de72f5 | |||
50e4b40721 | |||
2a8493fa69 | |||
ed98f772a4 | |||
8e6fbf3c9d | |||
2ca46ce746 | |||
ed02d10026 | |||
8e6e0c7846 | |||
c9903086fb | |||
73a64034b0 | |||
8e39f08cf5 | |||
ef4ba5a348 | |||
174fd80a73 | |||
e3617434e6 | |||
bba35875a3 | |||
956283f4a0 | |||
0acfd17165 | |||
fee19c61bc | |||
3051f37587 | |||
e12308d3cc | |||
65e909d053 | |||
0947858ffc | |||
326e7acb4b | |||
9e2f37477b | |||
dcfe6a00ea | |||
fc486190c2 | |||
72fef5686b | |||
361a221dc0 | |||
7609cbcccf | |||
acbd45d530 | |||
837c04c400 | |||
fde6fc2a89 | |||
1be0ec3dcd | |||
dcce352ffd | |||
3e426844a8 | |||
021514aabd | |||
00590e69ef | |||
118cf31ab1 | |||
91223b67a5 | |||
4f33d0bd5b | |||
79f9ab9950 | |||
c50c368d2f | |||
9f57b16d76 | |||
33b2bb1b20 | |||
e9f3a8bf86 | |||
794d0497c8 | |||
b185b098d0 | |||
fc07bb590a | |||
ed74a9f470 | |||
9f99ccd78f | |||
9e756e28e4 | |||
e59b7e8b7b | |||
2a7b7e7ac2 | |||
de078677fe | |||
634d0061d4 | |||
03a6809510 | |||
e95b8d93d8 | |||
0aa75704a2 | |||
5f246d7a51 | |||
ee3f2ca9d3 | |||
f20ed1d3d6 | |||
538c5054d6 | |||
c15f59db39 | |||
53f4d77ff3 | |||
c501ccdff1 | |||
2a6f047c28 | |||
a4f7c80fe2 | |||
aa20b81229 | |||
3f93973a12 | |||
e84f8096bd | |||
22b18bfbc9 | |||
c7250ae0a9 | |||
27ddf6f59f | |||
66039e56a6 | |||
d082d74716 | |||
1bc219a162 | |||
700f2bc8ae | |||
95e37e55eb | |||
087d50f61b | |||
2e12e4f39d | |||
b811e76318 | |||
331691e332 | |||
186a680e98 | |||
a99e0d93f0 | |||
a2507370cc | |||
a38b280098 | |||
bca92f1f71 | |||
6ce47ec2a7 | |||
dcd2c8aa06 | |||
561ebd5508 | |||
ae12fdeaec | |||
d30dd3c05a | |||
4b907f6121 | |||
6399afd799 | |||
5d9513bac0 | |||
5514ed4fd6 | |||
b8bb2c865e | |||
4395087a7c | |||
d8808df091 | |||
3cd07a38ca | |||
a89eb97c9d | |||
96ebd2c89f | |||
3596a8cdc7 | |||
8e465720bc | |||
098f11bd09 | |||
484adbbf49 | |||
0e6ed74682 | |||
c377ac8216 | |||
6f6ce2ad24 | |||
20ad5da89a | |||
4ba83e6834 | |||
8aa499a0b9 | |||
6b40d6be7e | |||
72821157dd | |||
9cc8d03376 | |||
9882e84aa6 | |||
ad7cdcb7fe | |||
26e27e3339 | |||
39f49fc539 | |||
343268d029 | |||
8ee80ea170 | |||
a14afd8e27 | |||
e4ee4589a8 | |||
acf8cf28e8 | |||
20ee47e596 | |||
16942d9d19 | |||
a619662585 | |||
c4bcc4b85b | |||
878079030d | |||
8d8e92b84e | |||
75704ef0da | |||
509bf44619 | |||
329f6a0259 | |||
1bbba5de3c | |||
323138bd98 | |||
404bd979f4 | |||
e152c9baca | |||
ea87bab896 | |||
410237d678 | |||
cd437ca036 | |||
42b8049ae5 | |||
e610963d4b | |||
c8e9804a15 | |||
0ea81b8f69 | |||
875eeaf4d4 | |||
2e6687329b | |||
c005da2e6d | |||
431d881962 | |||
a8be3a8bce | |||
1600589793 | |||
440f7140ff | |||
b7a42d9f6a | |||
26f2c46e5b | |||
04481122ce | |||
2f54ee75b7 | |||
eebb862332 | |||
8009c16f0c | |||
fb7119b235 | |||
7df8a6fa3b | |||
878f09ad0d | |||
9ad1507b53 | |||
8c3feaeccf | |||
76d544572c | |||
a74b133cfc | |||
f254e7bb1e | |||
c64d97fae6 | |||
05a3cf927b | |||
097deb52bd | |||
7784be1838 | |||
505d08bb08 | |||
b885411b2e | |||
ee757f1149 | |||
cde25c8a64 | |||
d3beb8bc53 | |||
977989ffed | |||
b02c55e52c | |||
458aac971d | |||
67e4272f3e | |||
47f7ff3490 | |||
893e841a87 | |||
83239a492d | |||
4c66ca2b01 | |||
b0bbdde112 | |||
816859c3e3 | |||
9e2e3ceb7f | |||
8b44a248cc | |||
48e274234e | |||
243f22542d | |||
f74213bafb | |||
588efcb908 | |||
c86854dcb1 | |||
d2319465c1 | |||
5d2c5a2053 | |||
5c003588fa | |||
70f187c431 | |||
ebfa0a71aa | |||
26323f996b | |||
1787604ac1 | |||
923207fdfa | |||
97c17c68a0 | |||
259eabfaaa | |||
3d6564f7a8 | |||
0b058565ba | |||
c3a20ad721 | |||
425517f0ae | |||
758f5bd134 | |||
9f4d97aa23 | |||
cebff0ef4e | |||
ee79e1753e | |||
2eaab49d35 | |||
db8eab1c5c | |||
38a9e154f8 | |||
c0dce6775e | |||
b2b7064ff0 | |||
6ae4cea8bb | |||
f383a5ed21 | |||
162b131435 | |||
7c1b8ca4d5 | |||
aeb73e2a5c | |||
e857032a73 | |||
8b8564309d | |||
ffeefd06c6 | |||
c0a7fe5209 | |||
6a28be5e3e | |||
0ad39c241e | |||
16c5d6961b | |||
3cdaa85511 | |||
334cca8197 | |||
902aed7278 | |||
feec48ed83 | |||
925fccbd17 | |||
0a7fc387ac | |||
e673bdb50c | |||
d08c15b9e7 | |||
c52531e8fc | |||
c18c9ec680 | |||
396ea97cad | |||
81e43286b5 | |||
ef4fd57927 | |||
a312f02fdc | |||
da1f5d8ab2 | |||
60208407ea | |||
f495774be4 | |||
cc51d943e2 | |||
f1bc9c18b9 | |||
1ec5161faf | |||
4847448a26 | |||
70f55f65b4 | |||
ccf7464d6f | |||
2b33bf44d2 | |||
d3e135ea15 | |||
74b2f935c3 | |||
02be024bc4 | |||
af1828a9e8 | |||
78d0b07c5c | |||
a2b647432c | |||
87806bd015 | |||
73fc1830ff | |||
fba6a50251 | |||
394c06a1c5 | |||
f4d0230166 | |||
e48826dd72 | |||
4879ba94b0 | |||
f7837dc190 | |||
fdb7d57cf0 | |||
1ce0429280 | |||
d6f02fb724 | |||
2e3fdf98a0 | |||
c633118774 | |||
2c73f60824 | |||
ada131a7c5 | |||
b13c221a96 | |||
806aa0f197 | |||
95bcc89aa6 | |||
b86362b366 | |||
6470704f43 | |||
1081dfb718 | |||
aad6393f9a | |||
b9a50f9e14 | |||
2af8a941ff | |||
66229e86be | |||
6d0ee4e03a | |||
94412a25bb | |||
426e104bbe | |||
fa251e60b6 | |||
34b9a80ba7 | |||
82b0d9a63a | |||
06cdebaccb | |||
c29a002c06 | |||
cb489d1eca | |||
a1378c98b4 | |||
e725f8d554 | |||
e43881e03f | |||
ff8bb761ee | |||
3a2a686b6c | |||
e6b37afa8c | |||
b144321c76 | |||
ae09f20910 | |||
ec16073539 | |||
0b10221fed | |||
2aefafa473 | |||
72946c3922 | |||
975da67d33 | |||
f2510ac8c0 | |||
4455284bdb | |||
5827626deb | |||
4ce4ea6f70 | |||
a4fadb218b | |||
f59b92e06c | |||
9eee8ba612 | |||
f783552820 | |||
c0f52ecf28 | |||
395ac3033f | |||
6af3a0e42b | |||
1baceb5816 | |||
0b6a0337fe | |||
4b08686393 | |||
2bac28a553 | |||
28037af029 | |||
4420fa588d | |||
db8e13ec35 | |||
1624fd5f63 | |||
705e3dfba1 | |||
dd498cf18d | |||
ddbecdb16f | |||
b879a7c38b | |||
38771eb7be | |||
b86aaf90a3 | |||
4469767d8f | |||
43a33e579d | |||
2bad17ebdb | |||
1352aba438 | |||
eee6421775 | |||
65c6caa13c | |||
e7a0fdae26 | |||
f3a5de67e7 | |||
532fe9f7e6 | |||
18bfd6605d | |||
0c5fc7e03f | |||
3e99a6336c | |||
80b9542c2d | |||
4a69084a8b | |||
7a5d97e153 | |||
e36498f709 | |||
5148f9162d | |||
620d1204f7 | |||
1a3f77175b | |||
db1d8383fd | |||
27c780602c | |||
83a966f1af |
6
.gitmodules
vendored
@ -4,3 +4,9 @@
|
||||
[submodule "php-markdown"]
|
||||
path = lib/php-markdown
|
||||
url = https://github.com/michelf/php-markdown.git
|
||||
[submodule "lib/chibi-sql"]
|
||||
path = lib/chibi-sql
|
||||
url = https://github.com/rr-/chibi-sql.git
|
||||
[submodule "lib/TextCaseConverter"]
|
||||
path = lib/TextCaseConverter
|
||||
url = https://gist.github.com/rr-/10522533.git
|
||||
|
120
data/config.ini
@ -1,22 +1,28 @@
|
||||
[chibi]
|
||||
prettyPrint=1
|
||||
|
||||
[main]
|
||||
dbDriver = "sqlite"
|
||||
dbLocation = "./data/db.sqlite"
|
||||
dbUser = "test"
|
||||
dbPass = "test"
|
||||
filesPath = "./data/files/"
|
||||
thumbsPath = "./data/thumbs/"
|
||||
logsPath = "./data/logs/"
|
||||
mediaPath = "./public_html/media/"
|
||||
title = "szurubooru"
|
||||
salt = "1A2/$_4xVa"
|
||||
dbDriver = "sqlite"
|
||||
dbLocation = "./data/db.sqlite"
|
||||
dbUser = "test"
|
||||
dbPass = "test"
|
||||
cachePath = "./cache/"
|
||||
logsPath = "./data/logs/{yyyy}-{mm}.log"
|
||||
filesPath = "./public_html/files/"
|
||||
mediaPath = "./public_html/media/"
|
||||
thumbnailsPath = "./public_html/thumbs/"
|
||||
avatarsPath = "./public_html/avatars/"
|
||||
salt = "1A2/$_4xVa"
|
||||
|
||||
[appearance]
|
||||
title = "szurubooru"
|
||||
;favicon = "/media/img/favicon.png"
|
||||
;extraScripts[] = "/media/scripts/extra1.js"
|
||||
;extraScripts[] = "/media/scripts/extra2.js"
|
||||
;extraStyles[] = "/media/scripts/extra.css"
|
||||
|
||||
[misc]
|
||||
featuredPostMaxDays=7
|
||||
proxyThumbsInUpload=0
|
||||
debugQueries=0
|
||||
logAnonymousUploads=1
|
||||
githubLink = http://github.com/rr-/szurubooru
|
||||
|
||||
[help]
|
||||
title=Help
|
||||
@ -31,19 +37,36 @@ paths[privacy]=./data/privacy.md
|
||||
usersPerPage=8
|
||||
postsPerPage=20
|
||||
logsPerPage=250
|
||||
thumbWidth=150
|
||||
thumbHeight=150
|
||||
thumbStyle=outside
|
||||
tagsPerPage=100
|
||||
tagsRelated=15
|
||||
thumbnailWidth=175
|
||||
thumbnailHeight=175
|
||||
thumbnailStyle=outside
|
||||
endlessScrollingDefault=1
|
||||
showPostTagTitlesDefault=0
|
||||
showDislikedPostsDefault=1
|
||||
maxSearchTokens=4
|
||||
maxRelatedPosts=50
|
||||
|
||||
[tags]
|
||||
minLength = 1
|
||||
maxLength = 64
|
||||
regex = "/^[()\[\]a-zA-Z0-9_.-]+$/i"
|
||||
|
||||
[posts]
|
||||
maxSourceLength = 200
|
||||
|
||||
[comments]
|
||||
minLength = 5
|
||||
maxLength = 2000
|
||||
commentsPerPage = 20
|
||||
commentsPerPage = 10
|
||||
maxCommentsInList = 5
|
||||
needEmailForCommenting = 0
|
||||
|
||||
[uploads]
|
||||
needEmailForUploading = 1
|
||||
logAnonymousUploadsNicknames = 1
|
||||
allowAnonymousUploads = 1
|
||||
|
||||
[registration]
|
||||
staffActivation = 0
|
||||
@ -54,8 +77,6 @@ userNameMaxLength = 20
|
||||
userNameRegex = "/^[\w_-]+$/ui"
|
||||
|
||||
needEmailForRegistering = 1
|
||||
needEmailForCommenting = 0
|
||||
needEmailForUploading = 1
|
||||
confirmationEmailEnabled = 1
|
||||
confirmationEmailSenderName = "{host} mailing system"
|
||||
confirmationEmailSenderEmail = "noreply@{host}"
|
||||
@ -67,46 +88,66 @@ passwordResetEmailSubject = "{host} - password reset"
|
||||
passwordResetEmailBody = "Hello,{nl}{nl}You received this e-mail because someone requested a password reset for user with this e-mail address at {host}. If it's you, visit {link} to finish password reset process, otherwise you may ignore and delete this e-mail.{nl}{nl}Kind regards,{nl}{host} mailing system"
|
||||
|
||||
[privileges]
|
||||
uploadPost=registered
|
||||
registerAccount=anonymous
|
||||
;registerAccount=nobody
|
||||
|
||||
listPosts=anonymous
|
||||
listPosts.safe=anonymous
|
||||
listPosts.sketchy=registered
|
||||
listPosts.unsafe=registered
|
||||
listPosts.hidden=admin
|
||||
listPosts.hidden=moderator
|
||||
|
||||
;privilege to view post page, e.g. example.com/post/53
|
||||
viewPost=anonymous
|
||||
viewPost.safe=anonymous
|
||||
viewPost.sketchy=registered
|
||||
viewPost.unsafe=registered
|
||||
viewPost.hidden=admin
|
||||
viewPost.hidden=moderator
|
||||
|
||||
retrievePost=anonymous
|
||||
favoritePost=registered
|
||||
|
||||
addPost=registered
|
||||
addPostSafety=registered
|
||||
addPostTags=registered
|
||||
addPostThumbnail=power-user
|
||||
addPostSource=registered
|
||||
addPostRelations=power-user
|
||||
addPostContent=registered
|
||||
|
||||
editPost=registered
|
||||
editPostSafety.own=registered
|
||||
editPostSafety.all=moderator
|
||||
editPostTags=registered
|
||||
editPostThumb=moderator
|
||||
editPostThumbnail=moderator
|
||||
editPostSource=moderator
|
||||
editPostRelations.own=registered
|
||||
editPostRelations.all=moderator
|
||||
editPostFile.all=moderator
|
||||
editPostFile.own=moderator
|
||||
hidePost.own=moderator
|
||||
hidePost.all=moderator
|
||||
deletePost.own=moderator
|
||||
deletePost.all=moderator
|
||||
editPostContent=moderator
|
||||
|
||||
massTag.own=registered
|
||||
massTag.all=power-user
|
||||
hidePost=moderator
|
||||
deletePost=moderator
|
||||
featurePost=moderator
|
||||
scorePost=registered
|
||||
flagPost=registered
|
||||
|
||||
listUsers=registered
|
||||
viewUser=registered
|
||||
viewUserEmail.all=admin
|
||||
viewUserEmail.own=registered
|
||||
changeUserPassword.own=registered
|
||||
changeUserPassword.all=admin
|
||||
changeUserEmail.own=registered
|
||||
changeUserEmail.all=admin
|
||||
changeUserAccessRank=admin
|
||||
changeUserName=moderator
|
||||
changeUserSettings.all=nobody
|
||||
changeUserSettings.own=registered
|
||||
viewUserEmail.all=admin
|
||||
editUserPassword.own=registered
|
||||
editUserPassword.all=admin
|
||||
editUserEmail.own=registered
|
||||
editUserEmail.all=admin
|
||||
editUserEmailNoConfirm=admin
|
||||
editUserAccessRank=admin
|
||||
editUserName=moderator
|
||||
editUserAvatar.own=registered
|
||||
editUserAvatar.all=admin
|
||||
editUserSettings.own=registered
|
||||
editUserSettings.all=nobody
|
||||
acceptUserRegistration=moderator
|
||||
banUser.own=nobody
|
||||
banUser.all=admin
|
||||
@ -124,7 +165,6 @@ editComment.all=admin
|
||||
listTags=anonymous
|
||||
mergeTags=moderator
|
||||
renameTags=moderator
|
||||
massTag=moderator
|
||||
|
||||
listLogs=moderator
|
||||
viewLog=moderator
|
||||
|
94
data/help.md
@ -6,44 +6,76 @@ If you’re not a registered user, you will only see public (Safe) posts. Lo
|
||||
|
||||
You can use your keyboard to navigate around the site. There are a few shortcuts:
|
||||
|
||||
- focus search field: `[Q]`
|
||||
- scroll up/down: `[W]` and `[S]`
|
||||
- go to newer/older post or page: `[A]` and `[D]`
|
||||
- edit post: `[E]`
|
||||
- focus first post in post list: `[P]`
|
||||
Hotkey | Description
|
||||
--------------- | -----------
|
||||
`[Q]` | Focus search field
|
||||
`[W]` and `[S]` | Scroll up / down
|
||||
`[A]` and `[D]` | Go to newer/older post or page
|
||||
`[E]` | Edit post
|
||||
`[P]` | Focus first post in post list
|
||||
|
||||
# Search syntax
|
||||
|
||||
- contatining tag "Haruhi": [search]Haruhi[/search]
|
||||
- **not** contatining tag "Kyon": [search]-Kyon[/search]
|
||||
- uploaded by David: [search]submit:David[/search] (note no spaces)
|
||||
- favorited by David: [search]fav:David[/search]
|
||||
- favorited by at least four users: [search]favmin:4[/search]
|
||||
- commented by David: [search]comment:David[/search]
|
||||
- having at least three comments: [search]commentmin:3[/search]
|
||||
- having minimum score of 4: [search]scoremin:4[/search]
|
||||
- tagged with at least seven tags: [search]tagmin:7[/search]
|
||||
- exactly from the specified date: [search]date:2001[/search], [search]date:2012-09-29[/search] (yyyy-mm-dd format)
|
||||
- from the specified date onwards: [search]datemin:2001-01-01[/search]
|
||||
- up to the specified date: [search]datemax:2004-07[/search]
|
||||
- having specific ID: [search]id:1,2,3,8[/search]
|
||||
- having ID no less than specified value: [search]idmin:28[/search]
|
||||
- by content type: [search]type:img[/search], [search]type:swf[/search], [search]type:yt[/search] (images, flash files and YouTube videos, respectively)
|
||||
- scored up/down by currently logged in user: [search]special:likes[/search] and [search]special:dislikes[/search]
|
||||
Command | Description | Aliases |
|
||||
--------------------------------- | --------------------------------------------------------- | ----------------------------------------------- |
|
||||
[search]Haruhi[/search] | containing tag "Haruhi" | - |
|
||||
[search]-Kyon[/search] | **not** containing tag "Kyon" | - |
|
||||
[search]submit:David[/search] | uploaded by user David | `upload`, `uploads`, `uploaded`, `uploader` |
|
||||
[search]comment:David[/search] | commented by David | `comments`, `commenter`, `commented` |
|
||||
[search]fav:David[/search] | favorited by David | `favs`, `favd` |
|
||||
[search]favmin:4[/search] | favorited by at least four users | `fav_min` |
|
||||
[search]favmax:4[/search] | favorited by at most four users | `fax_max` |
|
||||
[search]commentmin:3[/search] | having at least three comments | `comment_min` |
|
||||
[search]commentmax:3[/search] | having at most three comments | `comment_max` |
|
||||
[search]scoremin:4[/search] | having minimum score of 4 | `score_min` |
|
||||
[search]scoremax:4[/search] | having maximum score of 4 | `score_max` |
|
||||
[search]tagmin:7[/search] | tagged with at least seven tags | `tag_min` |
|
||||
[search]tagmax:7[/search] | tagged with at most seven tags | `tax_max` |
|
||||
[search]date:today[/search] | posted today | - |
|
||||
[search]date:yesterday[/search] | posted yesterday | - |
|
||||
[search]date:2000[/search] | posted in year 2000 | - |
|
||||
[search]date:2000-01[/search] | posted in January, 2000 | - |
|
||||
[search]date:2000-01-01[/search] | posted on January 1st, 2000 | - |
|
||||
[search]datemin:...[/search] | posted on `...` or later (format like in `date:`) | `date_min` |
|
||||
[search]datemax:...[/search] | posted on `...` or earlier (format like in `date:`) | `date_max` |
|
||||
[search]filesizemin:7M[/search] | has size of at least 7 megabytes | `filesize_min` |
|
||||
[search]filesizemax:30K[/search] | has size of at most 30 kilobytes | `filesize_max` |
|
||||
[search]imgsize:huge[/search] | either dimension has at least 2001px | `img_size`, `imagesize`, `image_size` |
|
||||
[search]imgsize:large[/search] | either dimension has at least 801px and at most 2000px | `img_size`, `imagesize`, `image_size` |
|
||||
[search]imgsize:medium[/search] | either dimension has at least 301px and at most 800px | `img_size`, `imagesize`, `image_size` |
|
||||
[search]imgsize:small[/search] | either dimension has at most 300px | `img_size`, `imagesize`, `image_size` |
|
||||
[search]id:1,2,3[/search] | having specific post ID | `ids` |
|
||||
[search]name:...[/search] | having specific post name (hash in full URLs) | `names`, `hash`, `hashes` |
|
||||
[search]idmin:5[/search] | posts with ID greater than or equal to @5 | `id_min` |
|
||||
[search]idmax:5[/search] | posts with ID less than or equal to @5 | `id_max` |
|
||||
[search]type:img[/search] | only image posts | `type:image` |
|
||||
[search]type:flash[/search] | only Flash posts | `type:swf` |
|
||||
[search]type:yt[/search] | only Youtube posts | `type:youtube` |
|
||||
[search]special:liked[/search] | posts liked by currently logged in user | `special:likes`, `special:like` |
|
||||
[search]special:disliked[/search] | posts disliked by currently logged in user | `special:dislikes`, `special:dislike` |
|
||||
[search]special:fav[/search] | posts added to favorites by currently logged in user | `special:favs`, `special:favd` |
|
||||
[search]special:hidden[/search] | hidden (soft-deleted) posts; moderators only | - |
|
||||
|
||||
You can combine tags and negate any of them for interesting results. [search]sea -favmin:8 type:swf submit:Pirate[/search] will show you **flash files** tagged as **sea**, that were **liked by seven people** at most, uploaded by user **Pirate**.
|
||||
|
||||
All of the above can be sorted using additional sorting tags:
|
||||
All of the above can be sorted using additional tag in form of `order:...`:
|
||||
|
||||
- as random as it can get: [search]order:random[/search]
|
||||
- newest to oldest: [search]order:date[/search] (pretty much default browse view)
|
||||
- oldest to newest: [search]-order:date[/search]
|
||||
- most commented first: [search]order:comments[/search]
|
||||
- loved by most: [search]order:favs[/search]
|
||||
- highest scored: [search]order:score[/search]
|
||||
- with most tags: [search]order:tags[/search]
|
||||
Command | Description | Aliases (`order:...`) |
|
||||
--------------------------------- | -------------------------------------------------------- | ------------------------------------------ |
|
||||
[search]order:random[/search] | as random as it can get | - |
|
||||
[search]order:id[/search] | highest to lowest post ID (default browse view) | - |
|
||||
[search]order:date[/search] | newest to oldest (pretty much same as above) | - |
|
||||
[search]-order:date[/search] | oldest to newest | - |
|
||||
[search]order:date,asc[/search] | oldest to newest (ascending order, default = descending) | - |
|
||||
[search]order:score[/search] | highest scored | - |
|
||||
[search]order:comments[/search] | most commented first | `comment`, `commentcount`, `comment_count` |
|
||||
[search]order:favs[/search] | loved by most | `fav`, `favcount`, `fav_count` |
|
||||
[search]order:tags[/search] | with most tags | `tag`, `tagcount`, `tag_count` |
|
||||
[search]order:commentdate[/search] | recently commented | `comment_date` |
|
||||
[search]order:favdate[/search] | recently added to favorites | `fav_date` |
|
||||
[search]order:filesize[/search] | largest files first | `file_size` |
|
||||
|
||||
As shown with [search]-order:date[/search], any of them can be reversed in the same way as negating other tags: by placing a dash before the tag. If there is a "min" tag, there’s also its "max" counterpart, e.g. [search]favmax:7[/search].
|
||||
As shown with [search]-order:date[/search], any of them can be reversed in the same way as negating other tags: by placing a dash before the tag.
|
||||
|
||||
# Registration
|
||||
|
||||
@ -62,3 +94,5 @@ Registered users can post comments. Comments support [Markdown syntax](http://da
|
||||
# Uploads
|
||||
|
||||
After registering and activating your account, you gain the power to upload files to the service for everyone else to see.
|
||||
|
||||
Remember to follow the [rules](/help/rules)!
|
||||
|
@ -8,4 +8,4 @@ Your actions related to posts (uploading, tagging, etc.) are logged, along with
|
||||
|
||||
# Cookies
|
||||
|
||||
Cookies are used to store your session data and browsing preferences, such as endless scrolling or visibility of NSFW posts.
|
||||
Cookies are used to store your session data in order to keep you logged in and personalize your web experience.
|
||||
|
83
init.php
@ -1,10 +1,22 @@
|
||||
<?php
|
||||
require_once 'src/core.php';
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$fontsPath = TextHelper::absolutePath($config->main->mediaPath . DS . 'fonts');
|
||||
$libPath = TextHelper::absolutePath($config->main->mediaPath . DS . 'lib');
|
||||
|
||||
function updateVersion()
|
||||
{
|
||||
$version = exec('git describe --tags --always --dirty');
|
||||
$branch = exec('git rev-parse --abbrev-ref HEAD');
|
||||
PropertyModel::set(PropertyModel::EngineVersion, $version . '@' . $branch);
|
||||
}
|
||||
|
||||
function getLibPath()
|
||||
{
|
||||
return TextHelper::absolutePath(Core::getConfig()->main->mediaPath . DS . 'lib');
|
||||
}
|
||||
|
||||
function getFontsPath()
|
||||
{
|
||||
return TextHelper::absolutePath(Core::getConfig()->main->mediaPath . DS . 'fonts');
|
||||
}
|
||||
|
||||
function download($source, $destination = null)
|
||||
{
|
||||
@ -26,37 +38,54 @@ function download($source, $destination = null)
|
||||
return $content;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//jQuery
|
||||
download('http://code.jquery.com/jquery-2.0.3.min.js', $libPath . DS . 'jquery' . DS . 'jquery.min.js');
|
||||
download('http://code.jquery.com/jquery-2.0.3.min.map', $libPath . DS . 'jquery' . DS . 'jquery.min.map');
|
||||
|
||||
//jQuery UI
|
||||
download('http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js', $libPath . DS . 'jquery-ui' . DS . 'jquery-ui.min.js');
|
||||
$manifest = download('http://ajax.googleapis.com/ajax/libs/jqueryui/1/MANIFEST');
|
||||
$lines = explode("\n", str_replace("\r", '', $manifest));
|
||||
foreach ($lines as $line)
|
||||
function downloadJquery()
|
||||
{
|
||||
if (preg_match('/themes\/flick\/(.*?) /', $line, $matches))
|
||||
$libPath = getLibPath();
|
||||
download('http://code.jquery.com/jquery-2.1.1.min.js', $libPath . DS . 'jquery' . DS . 'jquery.min.js');
|
||||
download('http://code.jquery.com/jquery-2.1.1.min.map', $libPath . DS . 'jquery' . DS . 'jquery.min.map');
|
||||
}
|
||||
|
||||
function downloadJqueryUi()
|
||||
{
|
||||
$libPath = getLibPath();
|
||||
download('http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js', $libPath . DS . 'jquery-ui' . DS . 'jquery-ui.min.js');
|
||||
$manifest = download('http://ajax.googleapis.com/ajax/libs/jqueryui/1/MANIFEST');
|
||||
$lines = explode("\n", str_replace("\r", '', $manifest));
|
||||
foreach ($lines as $line)
|
||||
{
|
||||
$srcUrl = 'http://ajax.googleapis.com/ajax/libs/jqueryui/1/' . $matches[0];
|
||||
$dstUrl = $libPath . DS . 'jquery-ui' . DS . $matches[1];
|
||||
download($srcUrl, $dstUrl);
|
||||
if (preg_match('/themes\/flick\/(.*?) /', $line, $matches))
|
||||
{
|
||||
$srcUrl = 'http://ajax.googleapis.com/ajax/libs/jqueryui/1/' . $matches[0];
|
||||
$dstUrl = $libPath . DS . 'jquery-ui' . DS . $matches[1];
|
||||
download($srcUrl, $dstUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//jQuery Tag-it!
|
||||
download('http://raw.github.com/aehlke/tag-it/master/css/jquery.tagit.css', $libPath . DS . 'tagit' . DS . 'jquery.tagit.css');
|
||||
download('http://raw.github.com/aehlke/tag-it/master/js/tag-it.min.js', $libPath . DS . 'tagit' . DS . 'jquery.tagit.js');
|
||||
function downloadJqueryTagIt()
|
||||
{
|
||||
$libPath = getLibPath();
|
||||
download('http://raw.github.com/aehlke/tag-it/master/css/jquery.tagit.css', $libPath . DS . 'tagit' . DS . 'jquery.tagit.css');
|
||||
download('http://raw.github.com/aehlke/tag-it/master/js/tag-it.min.js', $libPath . DS . 'tagit' . DS . 'jquery.tagit.js');
|
||||
}
|
||||
|
||||
//Mousetrap
|
||||
download('http://raw.github.com/ccampbell/mousetrap/master/mousetrap.min.js', $libPath . DS . 'mousetrap' . DS . 'mousetrap.min.js');
|
||||
|
||||
//fonts
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans.ttf', $fontsPath . DS . 'DroidSans.ttf');
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans-Bold.ttf', $fontsPath . DS . 'DroidSans-Bold.ttf');
|
||||
function downloadMousetrap()
|
||||
{
|
||||
$libPath = getLibPath();
|
||||
download('http://raw.github.com/ccampbell/mousetrap/master/mousetrap.min.js', $libPath . DS . 'mousetrap' . DS . 'mousetrap.min.js');
|
||||
}
|
||||
|
||||
function downloadFonts()
|
||||
{
|
||||
$fontsPath = getFontsPath();
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans.ttf', $fontsPath . DS . 'DroidSans.ttf');
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans-Bold.ttf', $fontsPath . DS . 'DroidSans-Bold.ttf');
|
||||
}
|
||||
|
||||
downloadJquery();
|
||||
downloadJqueryUi();
|
||||
downloadJqueryTagIt();
|
||||
downloadMousetrap();
|
||||
downloadFonts();
|
||||
|
||||
require_once 'upgrade.php';
|
||||
|
1
lib/TextCaseConverter
Submodule
1
lib/chibi-sql
Submodule
@ -1,10 +1,18 @@
|
||||
DirectorySlash Off
|
||||
Options -Indexes
|
||||
|
||||
ErrorDocument 403 /fatal-error/403
|
||||
ErrorDocument 404 /fatal-error/404
|
||||
ErrorDocument 500 /fatal-error/500
|
||||
|
||||
RewriteEngine On
|
||||
ErrorDocument 403 /dispatch.php?request=error/http&code=403
|
||||
ErrorDocument 404 /dispatch.php?request=error/http&code=404
|
||||
ErrorDocument 500 /dispatch.php?request=error/http&code=500
|
||||
RewriteCond %{DOCUMENT_ROOT}/thumbs/$1.thumb -f
|
||||
RewriteRule ^/?post/(.*)/thumb/?$ /thumbs/$1.thumb
|
||||
RewriteRule ^/?thumbs/(.*).thumb - [L,T=image/jpeg]
|
||||
|
||||
RewriteCond %{DOCUMENT_ROOT}/files/$1 -f
|
||||
RewriteRule ^/?post/(.*)/retrieve/?$ /files/$1
|
||||
RewriteRule ^/?files/(.*) - [L]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
@ -22,10 +30,12 @@ AddOutputFilterByType DEFLATE application/xhtml+xml
|
||||
AddOutputFilterByType DEFLATE application/rss+xml
|
||||
AddOutputFilterByType DEFLATE application/javascript
|
||||
AddOutputFilterByType DEFLATE application/x-javascript
|
||||
AddOutputFilterByType DEFLATE application/x-font-ttf
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
AddType text/plain .map
|
||||
AddType application/x-font-ttf .ttf
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_expires.c>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
require_once '../src/core.php';
|
||||
|
||||
$query = $_SERVER['REQUEST_URI'];
|
||||
\Chibi\Facade::run($query, new Bootstrap());
|
||||
$dispatcher = new Dispatcher();
|
||||
$dispatcher->run();
|
||||
|
2
public_html/files/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
@ -1,30 +1,30 @@
|
||||
form.auth {
|
||||
#content form {
|
||||
margin: 0 auto;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
form.auth label.left {
|
||||
width: 35%;
|
||||
#content form.register label {
|
||||
width: 12em;
|
||||
}
|
||||
|
||||
form.auth p {
|
||||
#content form p {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
form.auth .help {
|
||||
#content form .help {
|
||||
opacity: .5;
|
||||
margin-top: 1em;
|
||||
font-size: small;
|
||||
}
|
||||
form.auth .help p {
|
||||
#content form .help p {
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
form.auth .help label+div {
|
||||
#content form .help label+div {
|
||||
float: left;
|
||||
}
|
||||
form.auth .help ul {
|
||||
#content form .help ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -9,4 +9,5 @@ form.edit-comment textarea,
|
||||
form.add-comment textarea {
|
||||
width: 50em;
|
||||
height: 8em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
@ -1,3 +1,8 @@
|
||||
.post .thumb {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.comment-group .post-wrapper {
|
||||
float: left;
|
||||
}
|
||||
@ -26,3 +31,8 @@
|
||||
.small-screen .comment-group .comments {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.hellip {
|
||||
margin-bottom: 2em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -22,11 +22,8 @@
|
||||
.comment {
|
||||
clear: left;
|
||||
}
|
||||
.comment .date:before {
|
||||
content: ' on ';
|
||||
margin: 0 0.2em;
|
||||
}
|
||||
.comment .date {
|
||||
margin: 0 0.2em 0 0.75em;
|
||||
color: silver;
|
||||
}
|
||||
|
||||
@ -35,14 +32,14 @@
|
||||
.comment .delete {
|
||||
font-size: small;
|
||||
}
|
||||
.comment .edit:before,
|
||||
.comment .delete:before {
|
||||
.comment .edit a:before,
|
||||
.comment .delete a:before {
|
||||
margin-left: 0.2em;
|
||||
content: ' [';
|
||||
color: silver;
|
||||
}
|
||||
.comment .edit:after,
|
||||
.comment .delete:after {
|
||||
.comment .edit a:after,
|
||||
.comment .delete a:after {
|
||||
content: ']';
|
||||
color: silver;
|
||||
}
|
||||
@ -50,3 +47,10 @@
|
||||
.comment .delete a {
|
||||
color: silver;
|
||||
}
|
||||
|
||||
.comment .edit a:hover,
|
||||
.comment .delete a:hover,
|
||||
.comment .edit a:focus,
|
||||
.comment .delete a:focus {
|
||||
color: red;
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ body {
|
||||
color: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
font-family: 'Droid Sans', sans-serif;
|
||||
font-size: 12pt;
|
||||
}
|
||||
@ -33,7 +35,8 @@ body {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 0 1.5em;
|
||||
margin: 0 auto;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
|
||||
@ -70,8 +73,8 @@ body {
|
||||
}
|
||||
#top-nav li.main-nav-item a:focus,
|
||||
#top-nav li.main-nav-item a:hover {
|
||||
color: firebrick;
|
||||
border-bottom: 3px solid firebrick;
|
||||
color: hsl(0,70%,45%);
|
||||
border-bottom: 3px solid hsl(0,70%,45%);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@ -85,11 +88,10 @@ body {
|
||||
}
|
||||
#top-nav li.search input {
|
||||
border: 0;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
padding: 4px 10px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
padding: 0 10px;
|
||||
margin: 0;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
#top-nav li.safety {
|
||||
@ -110,8 +112,7 @@ body {
|
||||
float: left;
|
||||
width: 25px;
|
||||
line-height: 28px;
|
||||
margin: 5px -1px 5px 0;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
margin: 5px 4px 5px 0;
|
||||
}
|
||||
#top-nav li.safety a:after {
|
||||
content: ' ';
|
||||
@ -122,45 +123,51 @@ body {
|
||||
#top-nav li.safety a:focus,
|
||||
#top-nav li.safety a:hover { opacity: .7; }
|
||||
#top-nav li.safety a.inactive { opacity: 1; }
|
||||
#top-nav li.safety .safety-safe .enabled { background: #cfe6c2; background: linear-gradient(to bottom, #CFE6C2 0%, #80C670 100%); }
|
||||
#top-nav li.safety .safety-safe .disabled { background: #a0a0a0; background: linear-gradient(to bottom, #a0a0a0 0%, #808080 100%); }
|
||||
#top-nav li.safety .safety-sketchy .enabled { background: #f0f4c8; background: linear-gradient(to bottom, #F0F4C8 0%, #EBE57A 100%); }
|
||||
#top-nav li.safety .safety-sketchy .disabled { background: #a0a0a0; background: linear-gradient(to bottom, #a0a0a0 0%, #808080 100%); }
|
||||
#top-nav li.safety .safety-unsafe .enabled { background: #fbc6b6; background: linear-gradient(to bottom, #FBC6B6 0%, #F37865 100%); }
|
||||
#top-nav li.safety .safety-unsafe .disabled { background: #a0a0a0; background: linear-gradient(to bottom, #a0a0a0 0%, #808080 100%); }
|
||||
#top-nav li.safety .safety-safe a { background: #b2efa2; }
|
||||
#top-nav li.safety .safety-sketchy a { background: #f0e4a8; }
|
||||
#top-nav li.safety .safety-unsafe a { background: #fbc6b6; }
|
||||
#top-nav li.safety .enabled { box-shadow: inset 0 0 0 3px rgba(0,0,0,0.1); }
|
||||
#top-nav li.safety .disabled { opacity: .3; }
|
||||
|
||||
|
||||
|
||||
footer {
|
||||
footer .main-wrapper {
|
||||
text-align: center;
|
||||
margin: 1em 0;
|
||||
padding-top: 0.5em;
|
||||
border-top: 1px solid #eee;
|
||||
margin-top: 1em;
|
||||
font-size: small;
|
||||
color: silver;
|
||||
}
|
||||
footer span:not(:last-child):after {
|
||||
content: '\022C5';
|
||||
margin: 0 0.5em;
|
||||
footer span:not(:last-of-type):after {
|
||||
content: '\2000\022C5\2000';
|
||||
}
|
||||
footer a {
|
||||
color: silver;
|
||||
}
|
||||
footer .left {
|
||||
float: left;
|
||||
}
|
||||
footer .right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#sidebar {
|
||||
float: left;
|
||||
width: 256px;
|
||||
margin-right: 1em;
|
||||
width: 240px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
#sidebar h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#sidebar+#inner-content {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
#sidebar .key {
|
||||
padding-right: 0.5em;
|
||||
padding-right: 0.3em;
|
||||
}
|
||||
#sidebar .key-value {
|
||||
max-width: 100%;
|
||||
@ -169,23 +176,11 @@ footer a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#inner-content {
|
||||
overflow: hidden;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
.unit {
|
||||
padding: 1em;
|
||||
border: 1px solid #eee;
|
||||
margin: 1em 0;
|
||||
margin: 2.5em 0;
|
||||
}
|
||||
#inner-content .unit {
|
||||
border-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
#sidebar .unit {
|
||||
border-left: 0;
|
||||
padding-left: 0;
|
||||
#sidebar .unit:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
#small-screen { display: none; }
|
||||
|
||||
@ -195,14 +190,10 @@ footer a {
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
body #sidebar .unit {
|
||||
border: 1px solid #eee;
|
||||
border-bottom: 0;
|
||||
padding: 1em 1em 0 1em;
|
||||
}
|
||||
#inner-content {
|
||||
float: none;
|
||||
width: auto;
|
||||
margin-left: 0;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +215,7 @@ hr {
|
||||
}
|
||||
|
||||
a {
|
||||
color: firebrick;
|
||||
color: hsl(0,70%,45%);
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
}
|
||||
@ -239,7 +230,7 @@ i[class*='icon-'] {
|
||||
display: inline-block;
|
||||
}
|
||||
a i[class*='icon-'] {
|
||||
background-color: firebrick;
|
||||
background-color: hsl(0,70%,45%);
|
||||
}
|
||||
a:focus i[class*='icon-'],
|
||||
a:hover i[class*='icon-'] {
|
||||
@ -248,103 +239,150 @@ a:hover i[class*='icon-'] {
|
||||
|
||||
|
||||
|
||||
form.aligned input,
|
||||
form.aligned button {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
form.aligned label {
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
}
|
||||
form.aligned label.left {
|
||||
display: inline-block;
|
||||
padding-right: 1em;
|
||||
width: 5em;
|
||||
min-height: 1em;
|
||||
float: left;
|
||||
}
|
||||
form.aligned>div {
|
||||
margin-bottom: 0.5em;
|
||||
.form-row {
|
||||
margin: 0 0 0.5em 0;
|
||||
clear: left;
|
||||
}
|
||||
form.aligned label,
|
||||
form.aligned input,
|
||||
form.aligned select,
|
||||
form.aligned button {
|
||||
vertical-align: middle;
|
||||
line-height: 20px;
|
||||
}
|
||||
form.aligned label,
|
||||
form.aligned input,
|
||||
form.aligned select {
|
||||
padding: 5px;
|
||||
}
|
||||
form.aligned input[type=file] {
|
||||
padding: 5px 0;
|
||||
}
|
||||
form.aligned input[type=radio],
|
||||
form.aligned input[type=checkbox] {
|
||||
width: auto;
|
||||
max-width: auto;
|
||||
margin: 0 10px 0 0;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
line-height: 30px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.input-wrapper ul.tagit,
|
||||
.input-wrapper input,
|
||||
.input-wrapper textarea,
|
||||
.input-wrapper select {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
label {
|
||||
.form-row>label {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
padding-right: 1em;
|
||||
width: 7em;
|
||||
min-height: 1em;
|
||||
float: left;
|
||||
}
|
||||
label,
|
||||
input:not([type=radio]):not([type=checkbox]):not([type=file]),
|
||||
select,
|
||||
button {
|
||||
-webkit-box-sizing: border-box !important;
|
||||
-moz-box-sizing: border-box !important;
|
||||
box-sizing: border-box !important;
|
||||
vertical-align: middle;
|
||||
line-height: 24px;
|
||||
padding: 3px 5px;
|
||||
height: 30px;
|
||||
}
|
||||
label,
|
||||
input,
|
||||
select,
|
||||
button {
|
||||
select {
|
||||
font-family: inherit;
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 12pt;
|
||||
border-radius: 5px;
|
||||
padding: 5px 15px;
|
||||
line-height: 100%;
|
||||
color: white;
|
||||
background: hsl(0,70%,60%);
|
||||
border: 0;
|
||||
}
|
||||
button:hover {
|
||||
background-color: hsl(0,75%,50%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
padding: 5px 0;
|
||||
}
|
||||
input[type=radio],
|
||||
input[type=checkbox] {
|
||||
width: auto;
|
||||
max-width: auto;
|
||||
margin: auto 10px auto 0;
|
||||
padding: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.radiobox-wrapper input[type=radio],
|
||||
.checkbox-wrapper input[type=checkbox] {
|
||||
display: none;
|
||||
}
|
||||
.radiobox-wrapper input[type=radio]+span,
|
||||
.checkbox-wrapper input[type=checkbox]+span {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 0.5em;
|
||||
background-image: url('../img/icons.png');
|
||||
background-repeat: none;
|
||||
vertical-align: text-bottom;
|
||||
content: '';
|
||||
}
|
||||
.radiobox-wrapper input[type=radio]+span { background-position: -126px -21px; }
|
||||
.radiobox-wrapper input[type=radio]:checked+span { background-position: -105px -21px; }
|
||||
.checkbox-wrapper input[type=checkbox]+span { background-position: -84px -21px; }
|
||||
.checkbox-wrapper input[type=checkbox]:checked+span { background-position: -63px -21px; }
|
||||
|
||||
ul.tagit,
|
||||
select,
|
||||
textarea,
|
||||
input:not([type=radio]):not([type=checkbox]):not([type=file]) {
|
||||
background: white;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
ul.tagit {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
min-height: 32px;
|
||||
}
|
||||
ul.tagit li.tagit-new {
|
||||
padding: 1px 0 !important;
|
||||
line-height: normal !important;
|
||||
}
|
||||
ul.tagit li.tagit-choice {
|
||||
padding: 1px 20px 1px 5px !important;
|
||||
line-height: normal !important;
|
||||
}
|
||||
ul.tagit input {
|
||||
border: 0 !important;
|
||||
line-height: normal !important;
|
||||
height: auto !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
button {
|
||||
font-size: 115%;
|
||||
padding: 0.2em 0.7em;
|
||||
color: white;
|
||||
background: cornflowerblue;
|
||||
border: 0;
|
||||
.related-tags {
|
||||
padding: 0.5em;
|
||||
background: rgba(255,255,255,0.7);
|
||||
border-radius: 3px;
|
||||
font-size: 95%;
|
||||
line-height: 180%;
|
||||
}
|
||||
button:hover {
|
||||
background-color: royalblue;
|
||||
cursor: pointer;
|
||||
.related-tags ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
.related-tags p {
|
||||
float: left;
|
||||
margin: 0 1em 0 0;
|
||||
}
|
||||
.related-tags li {
|
||||
display: inline-block;
|
||||
margin: 0 1em 0 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tabs ul {
|
||||
list-style-type: none;
|
||||
margin: -4px 0 1em 0;
|
||||
margin: 0 0 1em 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-bottom: 3px solid #eee;
|
||||
}
|
||||
.tabs li {
|
||||
display: inline-block;
|
||||
@ -353,22 +391,25 @@ button:hover {
|
||||
.tabs li a {
|
||||
display: inline-block;
|
||||
padding: 0.5em 1em;
|
||||
margin: 5px 0 -1px 0;
|
||||
vertical-align: middle;
|
||||
border: 1px none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border: 3px solid rgba(238, 238, 238, 0);
|
||||
border-bottom: 3px solid #eee;
|
||||
color: silver;
|
||||
margin: 0 0 -3px 0;
|
||||
}
|
||||
.tabs li.selected a {
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: none;
|
||||
border: 3px solid #eee;
|
||||
border-bottom-color: rgba(238, 238, 238, 0);
|
||||
color: inherit;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.tabs li a:hover,
|
||||
.tabs li a:focus {
|
||||
color: firebrick;
|
||||
color: hsl(0,70%,45%);
|
||||
}
|
||||
.tab-content {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
@ -379,7 +420,7 @@ button:hover {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
max-width: 500px;
|
||||
margin: 2em auto !important;
|
||||
margin: 2em auto;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
@ -405,15 +446,7 @@ button:hover {
|
||||
clear: both;
|
||||
height: 1px; /* ghost top margin in firefox */
|
||||
width: 100%;
|
||||
margin: 0 0 -1px 0;
|
||||
}
|
||||
|
||||
pre.debug {
|
||||
margin-left: 1em;
|
||||
text-align: left;
|
||||
color: black;
|
||||
white-space: normal;
|
||||
text-indent: -1em;
|
||||
margin: -1px 0 0 0;
|
||||
}
|
||||
|
||||
.spoiler:before,
|
||||
@ -433,6 +466,9 @@ pre.debug {
|
||||
.spoiler:hover {
|
||||
color: black;
|
||||
}
|
||||
.spoiler:not(:hover) a {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
@ -450,3 +486,8 @@ blockquote>*:first-child {
|
||||
blockquote>*:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ui-state-default,
|
||||
.ui-state-default a {
|
||||
color: hsla(0,70%,45%,0.8) !important;
|
||||
}
|
||||
|
30
public_html/media/css/debug.css
Normal file
@ -0,0 +1,30 @@
|
||||
div.debug {
|
||||
background: #f5f5f5;
|
||||
font-size: 90%;
|
||||
margin: 1em 0;
|
||||
padding: 1em;
|
||||
color: black;
|
||||
text-align: left;
|
||||
}
|
||||
div.debug pre {
|
||||
margin: 0 1em 1em 1em;
|
||||
white-space: normal;
|
||||
text-indent: -1em;
|
||||
}
|
||||
div.debug pre.query {
|
||||
color: maroon;
|
||||
}
|
||||
div.debug pre.bindings {
|
||||
color: gray;
|
||||
}
|
||||
div.debug pre.bindings .value {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
margin-right: 1em;
|
||||
}
|
||||
div.debug pre.query span {
|
||||
background: rgba(255, 0, 0, 0.05);
|
||||
}
|
||||
div.debug pre.query span:hover {
|
||||
background: white;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
code {
|
||||
margin: 0 0.5em;
|
||||
}
|
@ -4,15 +4,21 @@
|
||||
|
||||
#content input {
|
||||
margin: 0 1em;
|
||||
height: 25px;
|
||||
vertical-align: middle;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 11pt;
|
||||
#content code {
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
#content .paginator-content {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
pre strong {
|
||||
#content code strong {
|
||||
background: #fee;
|
||||
}
|
||||
|
@ -34,6 +34,6 @@
|
||||
|
||||
.paginator li a:focus,
|
||||
.paginator li a:hover {
|
||||
border: 1px solid firebrick;
|
||||
border: 1px solid hsl(0,70%,50%);
|
||||
background: pink;
|
||||
}
|
||||
|
@ -1,28 +1,29 @@
|
||||
.post {
|
||||
margin: 0.5em;
|
||||
margin: 8px;
|
||||
}
|
||||
.posts-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
.posts {
|
||||
margin: 0 auto;
|
||||
margin: -8px auto 0 auto;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
text-align: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.small-screen .form-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
form.aligned {
|
||||
#content form {
|
||||
margin: 0 auto;
|
||||
width: 24em;
|
||||
text-align: left;
|
||||
}
|
||||
form.aligned label.left {
|
||||
width: 7em;
|
||||
#content form label {
|
||||
width: 9em;
|
||||
}
|
||||
form h1 {
|
||||
#content form h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -10,27 +10,39 @@
|
||||
}
|
||||
|
||||
.post-type-youtube:after,
|
||||
.post-type-flash:after {
|
||||
.post-type-swf:after,
|
||||
.post-type-mp4:after,
|
||||
.post-type-webm:after,
|
||||
.post-type-ogg:after,
|
||||
.post-type-3gp:after,
|
||||
.post-type-flv:after {
|
||||
position: absolute;
|
||||
right: 1px; /* border */
|
||||
top: 1px; /* border */
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
/* border */
|
||||
right: 1px;
|
||||
top: 1px;
|
||||
bottom: 1px;
|
||||
left: 1px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
content: ' ';
|
||||
pointer-events: none;
|
||||
}
|
||||
.post-type-flash {
|
||||
.post-type-youtube,
|
||||
.post-type-swf,
|
||||
.post-type-mp4,
|
||||
.post-type-webm,
|
||||
.post-type-ogg,
|
||||
.post-type-3gp,
|
||||
.post-type-flv {
|
||||
border-color: red;
|
||||
}
|
||||
.post-type-youtube {
|
||||
border-color: red;
|
||||
}
|
||||
.post-type-flash:after {
|
||||
background: url('../img/thumb-overlay-swf.png');
|
||||
}
|
||||
.post-type-youtube:after {
|
||||
background: url('../img/thumb-overlay-yt.png');
|
||||
}
|
||||
.post-type-swf:after { background-image: url('../img/thumb-overlay-swf.png'); }
|
||||
.post-type-youtube:after { background-image: url('../img/thumb-overlay-yt.png'); }
|
||||
.post-type-mp4:after { background-image: url('../img/thumb-overlay-mp4.png'); }
|
||||
.post-type-webm:after { background-image: url('../img/thumb-overlay-webm.png'); }
|
||||
.post-type-ogg:after { background-image: url('../img/thumb-overlay-ogg.png'); }
|
||||
.post-type-3gp:after { background-image: url('../img/thumb-overlay-3gp.png'); }
|
||||
.post-type-flv:after { background-image: url('../img/thumb-overlay-flv.png'); }
|
||||
|
||||
|
||||
.post .toggle-tag {
|
||||
@ -63,7 +75,7 @@
|
||||
|
||||
.post .link:focus,
|
||||
.post .link:hover {
|
||||
border: 1px solid firebrick;
|
||||
border: 1px solid hsl(0,70%,50%);
|
||||
box-shadow: 0.25em 0.25em pink;
|
||||
}
|
||||
.post .link:focus img.thumb,
|
||||
@ -77,13 +89,11 @@
|
||||
}
|
||||
.post img.thumb {
|
||||
display: inline-block;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.post .info-bar:before {
|
||||
border-top: 1px solid firebrick;
|
||||
border-top: 1px solid hsl(0,70%,50%);
|
||||
margin-bottom: -1px;
|
||||
content: '';
|
||||
display: block;
|
||||
|
@ -1,20 +1,8 @@
|
||||
.items .item {
|
||||
width: 30%;
|
||||
float: left;
|
||||
#upload-step1 {
|
||||
display: table;
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.items .sep {
|
||||
width: 3%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.tab {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.tab.url {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#file-handler-wrapper {
|
||||
display: table;
|
||||
width: 100%;
|
||||
@ -30,116 +18,133 @@
|
||||
}
|
||||
#file-handler.active {
|
||||
background: #eee;
|
||||
border-color: firebrick;
|
||||
border-color: hsl(0,70%,50%);
|
||||
}
|
||||
|
||||
#url-handler textarea {
|
||||
width: 100%;
|
||||
height: 10em;
|
||||
margin-bottom: 0.5em;
|
||||
#url-handler {
|
||||
margin-top: 0.5em;
|
||||
position: relative;
|
||||
}
|
||||
#url-handler .input-wrapper {
|
||||
margin-right: 8.5em;
|
||||
}
|
||||
#url-handler button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
.post .thumbnail {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
line-height: 150px;
|
||||
#hybrid-view {
|
||||
text-align: center;
|
||||
}
|
||||
.thumbnail img {
|
||||
background-image: url('../img/thumb.jpg');
|
||||
background-size: 150px 150px;
|
||||
border: 1px solid black;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
#posts-wrapper {
|
||||
width: 40%;
|
||||
margin-right: 1em;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.post .alert,
|
||||
#upload-step2,
|
||||
#post-template {
|
||||
display: none;
|
||||
#posts {
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.post {
|
||||
margin: 2em 0;
|
||||
#posts td,
|
||||
#posts th {
|
||||
padding: 0.2em 0.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.post .ops {
|
||||
float: right;
|
||||
#posts th {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.post .ops a {
|
||||
cursor: pointer;
|
||||
margin-left: 0.5em;
|
||||
vertical-align: middle;
|
||||
#posts tr.selected {
|
||||
background: lemonchiffon;
|
||||
}
|
||||
.post a span {
|
||||
margin-left: 0.25em;
|
||||
vertical-align: middle;
|
||||
#posts .checkbox {
|
||||
width: 30px;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
|
||||
.post .move-up-trigger,
|
||||
.post .move-down-trigger {
|
||||
color: rgba(0, 64, 128, 0.5);
|
||||
#posts .checkbox input {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.post .move-up-trigger:hover,
|
||||
.post .move-down-trigger:hover {
|
||||
color: #00f;
|
||||
#posts .thumbnail {
|
||||
width: 40px;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
.post .move-up-trigger span,
|
||||
.post .move-down-trigger span {
|
||||
color: rgba(0, 64, 128, 1);
|
||||
}
|
||||
|
||||
.post:first-child .move-up-trigger {
|
||||
display: none;
|
||||
}
|
||||
.post:last-child .move-down-trigger {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.post .remove-trigger {
|
||||
color: rgba(128, 0, 0, 0.5);
|
||||
}
|
||||
.post .remove-trigger:hover {
|
||||
color: #f00;
|
||||
}
|
||||
.post .remove-trigger span {
|
||||
color: rgba(128, 0, 0, 1);
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
.post label {
|
||||
line-height: 33px;
|
||||
}
|
||||
.post label.left {
|
||||
display: inline-block;
|
||||
#posts .safety {
|
||||
width: 60px;
|
||||
padding-right: 10px;
|
||||
float: left;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
.post .safety label:not(.left) {
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
|
||||
.post .file-name strong {
|
||||
#posts .tags {
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 50%;
|
||||
white-space: pre;
|
||||
}
|
||||
#posts .safety {
|
||||
text-align: center;
|
||||
}
|
||||
#posts .safety [class^=safety-] {
|
||||
box-shadow: inset 0 0 0 3px rgba(0,0,0,0.1);
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#posts .safety-safe { background: #b2efa2; }
|
||||
#posts .safety-sketchy { background: #f0e4a8; }
|
||||
#posts .safety-unsafe { background: #fbc6b6; }
|
||||
#posts .thumbnail img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 33px;
|
||||
margin: 0;
|
||||
background-size: 30px 30px;
|
||||
}
|
||||
#post-ops {
|
||||
list-style-type: none;
|
||||
margin: 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
#post-ops li {
|
||||
display: inline-block;
|
||||
margin: 0 1em 0 0;
|
||||
}
|
||||
|
||||
.safety-safe {
|
||||
color: #43aa43;
|
||||
|
||||
#post-edit-form-wrapper {
|
||||
display: inline-block;
|
||||
width: 57.5%;
|
||||
}
|
||||
.safety-sketchy {
|
||||
color: #d4a627;
|
||||
#post-edit-form-wrapper p {
|
||||
margin-top: 0;
|
||||
}
|
||||
.safety-unsafe {
|
||||
color: #df4b0d;
|
||||
#post-edit-form {
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
}
|
||||
#post-edit-form .thumbnail img {
|
||||
max-width: 100%;
|
||||
max-height: 300px;
|
||||
margin: 0 auto 1em auto;
|
||||
}
|
||||
#post-edit-form .file-name strong {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
#post-edit-form,
|
||||
#upload-step2,
|
||||
#posts-wrapper,
|
||||
.alert,
|
||||
.template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul.tagit {
|
||||
@ -149,10 +154,48 @@ ul.tagit {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
#the-submit-wrapper {
|
||||
text-align: center;
|
||||
clear: both;
|
||||
}
|
||||
#the-submit {
|
||||
margin: 0 0 0 205px;
|
||||
margin: 1em auto;
|
||||
font-size: 14.5pt;
|
||||
padding: 0.35em 2em;
|
||||
height: auto;
|
||||
line-height: auto;
|
||||
}
|
||||
|
||||
.post .form-wrapper {
|
||||
overflow: hidden;
|
||||
#lightbox {
|
||||
display: none;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#lightbox img {
|
||||
max-width: 400px;
|
||||
max-height: 400px;
|
||||
background: white;
|
||||
border: 0.5em solid white;
|
||||
box-shadow: 0 0 0 1px #eee;
|
||||
position: relative;
|
||||
}
|
||||
#lightbox:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: white;
|
||||
border-left: 1px solid #eee;
|
||||
border-bottom: 1px solid #eee;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
#uploading-alert {
|
||||
display: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -4,17 +4,15 @@
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
img,
|
||||
embed {
|
||||
.post-type-image img,
|
||||
.post-type-video video {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.post-type-image img {
|
||||
/*background: url('../img/bk-image.png') lemonchiffon;*/
|
||||
}
|
||||
.post-type-flash iframe {
|
||||
.post-type-youtube iframe {
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
border: 0;
|
||||
/*background: url('../img/bk-swf.png') lemonchiffon;*/
|
||||
}
|
||||
|
||||
#sidebar .relations ul,
|
||||
@ -23,13 +21,28 @@ embed {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#sidebar .tags li {
|
||||
#sidebar .tags .tag-wrapper {
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
#sidebar .tags li a {
|
||||
padding-right: 2.75em;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
#sidebar .tags li .count {
|
||||
padding-left: 0.5em;
|
||||
position: absolute;
|
||||
width: 2em;
|
||||
right: 0;
|
||||
top: 0;
|
||||
color: silver;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
#around {
|
||||
@ -55,13 +68,24 @@ embed {
|
||||
background-color: silver;
|
||||
}
|
||||
|
||||
#sidebar .uploader img {
|
||||
vertical-align: middle;
|
||||
margin: 0 0.5em 0 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-image: url('http://www.gravatar.com/avatar/0?f=y&d=mm&s=16');
|
||||
#sidebar .uploader .date {
|
||||
font-size: 9pt !important;
|
||||
color: gray;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
}
|
||||
#sidebar .uploader img {
|
||||
vertical-align: text-top;
|
||||
float: left;
|
||||
margin: 3px 8px 0 0;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-image: url('http://www.gravatar.com/avatar/0?f=y&d=mm&s=25');
|
||||
}
|
||||
|
||||
#sidebar .unit.details { margin-bottom: 1.5em; }
|
||||
#sidebar .unit.hl-options { margin-top: 1.5em; }
|
||||
|
||||
#sidebar .safety-safe {
|
||||
color: #43aa43;
|
||||
@ -76,21 +100,51 @@ embed {
|
||||
#sidebar .score .selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
#sidebar .score a:first-of-type:before {
|
||||
content: '[';
|
||||
color: black;
|
||||
}
|
||||
#sidebar .score a:last-of-type:after {
|
||||
content: ']';
|
||||
color: black;
|
||||
}
|
||||
|
||||
#sidebar .permalink {
|
||||
display: inline-block;
|
||||
margin: 1em 0 0 -1px;
|
||||
width: 100%;
|
||||
font-size: 85%;
|
||||
line-height: 150%;
|
||||
height: auto;
|
||||
padding: 0.2em 0.5em;
|
||||
cursor: text;
|
||||
color: dimgray;
|
||||
border: 1px solid #e0e0e0;
|
||||
box-sizing: border-box;
|
||||
text-overflow: ellipsis;
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#sidebar .left a,
|
||||
#sidebar .right a {
|
||||
display: inline-block;
|
||||
}
|
||||
i.icon-prev {
|
||||
background-position: -12px -1px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
i.icon-next {
|
||||
background-position: -1px -1px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
i.icon-prev,
|
||||
i.icon-next {
|
||||
margin: 0 8px;
|
||||
vertical-align: middle;
|
||||
width: 8px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
i.icon-dl {
|
||||
margin: 0;
|
||||
width: 20px;
|
||||
@ -98,14 +152,33 @@ i.icon-dl {
|
||||
background-position: -22px -1px;
|
||||
}
|
||||
|
||||
.permalink {
|
||||
margin: 1em 0;
|
||||
i.icon-edit {
|
||||
margin: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-position: -43px -22px;
|
||||
}
|
||||
.permalink .icon-dl {
|
||||
|
||||
i.icon-fav {
|
||||
margin: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.add-fav i.icon-fav {
|
||||
background-position: -1px -22px;
|
||||
}
|
||||
.rem-fav i.icon-fav {
|
||||
background-position: -22px -22px;
|
||||
}
|
||||
|
||||
.hl-option {
|
||||
margin: 0.4em 0;
|
||||
}
|
||||
.hl-option i[class^='icon'] {
|
||||
vertical-align: middle;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.permalink span {
|
||||
.hl-option span {
|
||||
padding-left: 0.4em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.permalink .ext:after {
|
||||
@ -133,11 +206,27 @@ i.icon-dl {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#inner-content {
|
||||
position: relative;
|
||||
}
|
||||
.unit.edit-post {
|
||||
position: absolute;
|
||||
margin-top: 0;
|
||||
padding: 1em;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 0 0 1em 1em rgba(255, 255, 255, 0.8);
|
||||
z-index: 99;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: none;
|
||||
}
|
||||
form.edit-post .safety label:not(.left) {
|
||||
margin-right: 0.75em;
|
||||
.unit.edit-post ul.tagit,
|
||||
.unit.edit-post input:not([type=file]) {
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
.unit.edit-post ul.tagit input {
|
||||
background: transparent;
|
||||
}
|
||||
ul.tagit {
|
||||
display: block;
|
||||
|
18
public_html/media/css/static-api.css
Normal file
@ -0,0 +1,18 @@
|
||||
#content pre {
|
||||
background: ghostwhite;
|
||||
padding: 0.5em;
|
||||
border-left: 0.2em solid silver;
|
||||
}
|
||||
|
||||
#content table {
|
||||
border-spacing: 0;
|
||||
border-collapse: collapsue;
|
||||
}
|
||||
#content th,
|
||||
#content td {
|
||||
text-align: left;
|
||||
padding: 0.2em 0.5em;
|
||||
}
|
||||
#content tbody:nth-child(2n) {
|
||||
background: #fafafa;
|
||||
}
|
22
public_html/media/css/static-help.css
Normal file
@ -0,0 +1,22 @@
|
||||
.tab-content {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
border: 3px solid #eee;
|
||||
}
|
||||
table th {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
table td {
|
||||
padding: 0.1em 0.5em;
|
||||
}
|
||||
table th {
|
||||
text-align: left;
|
||||
background: #eee;
|
||||
}
|
||||
table td:first-child {
|
||||
white-space: pre;
|
||||
font-family: verdana;
|
||||
}
|
@ -16,16 +16,15 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#content {
|
||||
#content .main-wrapper>* {
|
||||
margin: 0 auto;
|
||||
width: 70%;
|
||||
min-width: 500px;
|
||||
position: relative;
|
||||
}
|
||||
.small-screen #content {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 500px;
|
||||
@media only screen and (max-width:700px) {
|
||||
#content .main-wrapper>* {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#content .body {
|
||||
@ -45,7 +44,7 @@
|
||||
#content .footer {
|
||||
font-size: small;
|
||||
color: dimgray;
|
||||
margin: 0.5em 0 3em 0;
|
||||
margin: 0.5em auto 3em auto;
|
||||
}
|
||||
#content .footer .left {
|
||||
float: left;
|
@ -17,22 +17,15 @@
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
width: 50%;
|
||||
max-width: 24em;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
.small-screen .form-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
form.aligned {
|
||||
text-align: left;
|
||||
margin: 0 auto;
|
||||
#content form label {
|
||||
width: 9em;
|
||||
}
|
||||
form.aligned label.left {
|
||||
width: 7em;
|
||||
}
|
||||
form h1 {
|
||||
#content form h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -61,5 +54,5 @@ nav.sort-styles li {
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
nav.sort-styles li.active {
|
||||
border-bottom: 3px solid firebrick;
|
||||
border-bottom: 3px solid hsl(0,70%,50%);
|
||||
}
|
||||
|
@ -1,27 +1,3 @@
|
||||
.user img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
float: left;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.user h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.user {
|
||||
line-height: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
margin-right: 1em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.user .details {
|
||||
display: inline-block;
|
||||
max-width: 25em;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
nav.sort-styles ul {
|
||||
list-style-type: none;
|
||||
margin: 0 0 2.5em 0;
|
||||
@ -35,5 +11,38 @@ nav.sort-styles li {
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
nav.sort-styles li.active {
|
||||
border-bottom: 3px solid firebrick;
|
||||
border-bottom: 3px solid hsl(0,70%,50%);
|
||||
}
|
||||
|
||||
.users-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user {
|
||||
text-align: initial;
|
||||
line-height: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
margin-right: 1em;
|
||||
float: left;
|
||||
white-space: pre;
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
.user a.avatar {
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
.user img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.user .details {
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.user h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
#sidebar {
|
||||
width: 220px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
@ -13,22 +12,12 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form.settings label.left,
|
||||
form.delete label.left,
|
||||
form.edit label.left {
|
||||
width: 9em;
|
||||
#content form {
|
||||
max-width: 30em;
|
||||
}
|
||||
|
||||
form.settings .alert,
|
||||
form.delete .alert,
|
||||
form.edit .alert {
|
||||
#content form label {
|
||||
width: 10em;
|
||||
}
|
||||
#content form .alert {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
form.settings input,
|
||||
form.delete input,
|
||||
form.edit select,
|
||||
form.edit input {
|
||||
width: 16em;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 2.5 KiB |
BIN
public_html/media/img/thumb-overlay-3gp.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
public_html/media/img/thumb-overlay-flv.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public_html/media/img/thumb-overlay-mp4.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
public_html/media/img/thumb-overlay-ogg.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
public_html/media/img/thumb-overlay-webm.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
@ -3,15 +3,15 @@ $(function()
|
||||
function onDomUpdate()
|
||||
{
|
||||
$('form.edit-comment textarea, form.add-comment textarea')
|
||||
.bind('change keyup', function(e)
|
||||
{
|
||||
enableExitConfirmation();
|
||||
});
|
||||
.bindOnce('exit-confirmation', 'change keyp', function(e)
|
||||
{
|
||||
enableExitConfirmation();
|
||||
});
|
||||
|
||||
$('form.edit-comment, form.add-comment').submit(function(e)
|
||||
$('form.edit-comment, form.add-comment')
|
||||
.bindOnce('comment-submit', 'submit', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
rememberLastSearchQuery();
|
||||
|
||||
var formDom = $(this);
|
||||
if (formDom.hasClass('inactive'))
|
||||
@ -19,7 +19,7 @@ $(function()
|
||||
formDom.addClass('inactive');
|
||||
formDom.find(':input').attr('readonly', true);
|
||||
|
||||
var url = formDom.attr('action') + '?json';
|
||||
var url = formDom.attr('action');
|
||||
var fd = new FormData(formDom[0]);
|
||||
|
||||
var preview = false;
|
||||
@ -35,77 +35,80 @@ $(function()
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
|
||||
success: function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
if (preview)
|
||||
{
|
||||
if (preview)
|
||||
{
|
||||
formDom.find('.preview').html(data['textPreview']).show();
|
||||
}
|
||||
else
|
||||
{
|
||||
disableExitConfirmation();
|
||||
|
||||
formDom.find('.preview').hide();
|
||||
var cb = function()
|
||||
{
|
||||
$.get(window.location.href, function(data)
|
||||
{
|
||||
$('.comments-wrapper').replaceWith($(data).find('.comments-wrapper'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
}
|
||||
if (formDom.hasClass('add-comment'))
|
||||
{
|
||||
cb();
|
||||
formDom.find('textarea').val('');
|
||||
}
|
||||
else
|
||||
{
|
||||
formDom.slideUp(function()
|
||||
{
|
||||
cb();
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
formDom.find('.preview').html(data['textPreview']).show();
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['message']);
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
disableExitConfirmation();
|
||||
|
||||
formDom.find('.preview').hide();
|
||||
var cb = function()
|
||||
{
|
||||
getHtml(window.location.href).success(function(data)
|
||||
{
|
||||
$('.comments-wrapper').replaceWith($(data).find('.comments-wrapper'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
}
|
||||
if (formDom.hasClass('add-comment'))
|
||||
{
|
||||
cb();
|
||||
formDom.find('textarea').val('');
|
||||
}
|
||||
else
|
||||
{
|
||||
formDom.slideUp(function()
|
||||
{
|
||||
cb();
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
},
|
||||
error: function()
|
||||
error: function(xhr)
|
||||
{
|
||||
alert('Fatal error');
|
||||
alert(xhr.responseJSON
|
||||
? xhr.responseJSON.message
|
||||
: 'Fatal error');
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
};
|
||||
|
||||
$.ajax(ajaxData);
|
||||
postJSON(ajaxData);
|
||||
});
|
||||
|
||||
$('.comment .edit a').click(function(e)
|
||||
$('.comment .edit a').bindOnce('edit-comment', 'click', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var commentDom = $(this).parents('.comment');
|
||||
$.get($(this).attr('href'), function(data)
|
||||
var formDom = commentDom.find('form.edit-comment');
|
||||
var cb = function(formDom)
|
||||
{
|
||||
commentDom.find('form.edit-comment').remove();
|
||||
var otherForm = $(data).find('form.edit-comment');
|
||||
otherForm.hide();
|
||||
commentDom.find('.body').append(otherForm);
|
||||
otherForm.slideDown();
|
||||
formDom.slideToggle();
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
};
|
||||
|
||||
if (formDom.length == 0)
|
||||
{
|
||||
getHtml($(this).attr('href')).success(function(data)
|
||||
{
|
||||
var otherForm = $(data).find('form.edit-comment');
|
||||
otherForm.hide();
|
||||
commentDom.find('.body').append(otherForm);
|
||||
formDom = commentDom.find('form.edit-comment');
|
||||
cb(formDom);
|
||||
});
|
||||
}
|
||||
else
|
||||
cb(formDom);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ function setCookie(name, value, exdays)
|
||||
|
||||
function getCookie(name)
|
||||
{
|
||||
console.log(document.cookie);
|
||||
var value = document.cookie;
|
||||
var start = value.indexOf(' ' + name + '=');
|
||||
|
||||
@ -26,45 +25,45 @@ function getCookie(name)
|
||||
return unescape(value.substring(start, end));
|
||||
}
|
||||
|
||||
function rememberLastSearchQuery()
|
||||
//core functionalities, prototypes
|
||||
function getJSON(data)
|
||||
{
|
||||
//lastSearchQuery variable is obtained from layout
|
||||
setCookie('last-search-query', lastSearchQuery);
|
||||
if (typeof(data.headers) === 'undefined')
|
||||
data.headers = {};
|
||||
data.headers['X-Ajax'] = '1';
|
||||
data.type = 'GET';
|
||||
return $.ajax(data);
|
||||
};
|
||||
|
||||
function postJSON(data)
|
||||
{
|
||||
if (typeof(data.headers) === 'undefined')
|
||||
data.headers = {};
|
||||
data.headers['X-Ajax'] = '1';
|
||||
data.type = 'POST';
|
||||
return $.ajax(data);
|
||||
};
|
||||
|
||||
function getHtml(data)
|
||||
{
|
||||
return $.get(data);
|
||||
}
|
||||
|
||||
//core functionalities, prototypes
|
||||
$.fn.hasAttr = function(name)
|
||||
{
|
||||
return this.attr(name) !== undefined;
|
||||
};
|
||||
|
||||
|
||||
|
||||
//safety trigger
|
||||
$(function()
|
||||
$.fn.bindOnce = function(name, eventName, callback)
|
||||
{
|
||||
$('.safety a').click(function(e)
|
||||
$.each(this, function(i, item)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var aDom = $(this);
|
||||
if (aDom.hasClass('inactive'))
|
||||
if ($(item).data(name) == name)
|
||||
return;
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var url = $(this).attr('href') + '?json';
|
||||
$.get(url).always(function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
window.location.reload();
|
||||
else
|
||||
{
|
||||
alert(data['message'] ? data['message'] : 'Fatal error');
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
$(item).data(name, name);
|
||||
$(item).on(eventName, callback);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -80,52 +79,94 @@ $(function()
|
||||
{
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
$('form.confirmable').submit(confirmEvent);
|
||||
$('a.confirmable').click(confirmEvent);
|
||||
|
||||
$('form.confirmable').bindOnce('confirmation', 'submit', confirmEvent);
|
||||
$('a.confirmable').bindOnce('confirmation', 'click', confirmEvent);
|
||||
|
||||
//simple action buttons
|
||||
$('a.simple-action').click(function(e)
|
||||
$('a.simple-action').bindOnce('simple-action', 'click', function(e)
|
||||
{
|
||||
if(e.isPropagationStopped())
|
||||
if (e.isPropagationStopped())
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
rememberLastSearchQuery();
|
||||
|
||||
var aDom = $(this);
|
||||
if (aDom.hasClass('inactive'))
|
||||
return;
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var url = $(this).attr('href') + '?json';
|
||||
$.get(url, {submit: 1}).always(function(data)
|
||||
var url = $(this).attr('href');
|
||||
postJSON({ url: url }).success(function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
if (aDom.hasAttr('data-redirect-url'))
|
||||
window.location.href = aDom.attr('data-redirect-url');
|
||||
else if (aDom.data('callback'))
|
||||
aDom.data('callback')();
|
||||
else
|
||||
window.location.reload();
|
||||
}).error(function(xhr)
|
||||
{
|
||||
alert(xhr.responseJSON
|
||||
? xhr.responseJSON.message
|
||||
: 'Fatal error');
|
||||
aDom.removeClass('inactive');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//simple action forms
|
||||
$('form.simple-action').bindOnce('simple-action', 'submit', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var formDom = $(this);
|
||||
if (formDom.hasClass('inactive'))
|
||||
return;
|
||||
formDom.addClass('inactive');
|
||||
formDom.find(':input').attr('readonly', true);
|
||||
|
||||
var url = formDom.attr('action');
|
||||
var fd = new FormData(formDom[0]);
|
||||
|
||||
var ajaxData =
|
||||
{
|
||||
url: url,
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
};
|
||||
|
||||
postJSON(ajaxData)
|
||||
.success(function(data)
|
||||
{
|
||||
if (aDom.hasAttr('data-redirect-url'))
|
||||
window.location.href = aDom.attr('data-redirect-url');
|
||||
else if (aDom.data('callback'))
|
||||
aDom.data('callback')();
|
||||
if (data.message)
|
||||
alert(data.message);
|
||||
disableExitConfirmation();
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
if (data.redirectUrl)
|
||||
window.location.href = data.redirectUrl;
|
||||
else
|
||||
window.location.reload();
|
||||
}
|
||||
else
|
||||
})
|
||||
.error(function(xhr)
|
||||
{
|
||||
alert(data['message'] ? data['message'] : 'Fatal error');
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
alert(xhr.responseJSON
|
||||
? xhr.responseJSON.message
|
||||
: 'Fatal error');
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//attach data from submit buttons to forms before .submit() gets called
|
||||
$('.submit').each(function()
|
||||
{
|
||||
$(this).click(function()
|
||||
$(this).bindOnce('submit-faux-input', 'click', function()
|
||||
{
|
||||
var form = $(this).closest('form');
|
||||
form.find('.faux-submit').remove();
|
||||
@ -137,10 +178,6 @@ $(function()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//try to remember last search query
|
||||
window.onbeforeunload = rememberLastSearchQuery;
|
||||
});
|
||||
|
||||
|
||||
@ -157,9 +194,6 @@ $(function()
|
||||
{
|
||||
$(window).resize(function()
|
||||
{
|
||||
if ($('body').width() == $('body').data('last-width'))
|
||||
return;
|
||||
$('body').data('last-width', $('body').width());
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
$('body').bind('dom-update', processSidebar);
|
||||
@ -173,9 +207,28 @@ function split(val)
|
||||
return val.split(/\s+/);
|
||||
}
|
||||
|
||||
function extractLast(term)
|
||||
function retrieveTags(searchTerm, cb)
|
||||
{
|
||||
return split(term).pop();
|
||||
var options =
|
||||
{
|
||||
url: '/tags-autocomplete',
|
||||
data: { search: searchTerm }
|
||||
};
|
||||
getJSON(options)
|
||||
.success(function(data)
|
||||
{
|
||||
var tags = $.map(data.tags.slice(0, 15), function(tag)
|
||||
{
|
||||
var ret =
|
||||
{
|
||||
label: tag.name + ' (' + tag.count + ')',
|
||||
value: tag.name,
|
||||
};
|
||||
return ret;
|
||||
});
|
||||
|
||||
cb(tags);
|
||||
});
|
||||
}
|
||||
|
||||
$(function()
|
||||
@ -187,12 +240,10 @@ $(function()
|
||||
minLength: 1,
|
||||
source: function(request, response)
|
||||
{
|
||||
var term = extractLast(request.term);
|
||||
var terms = split(request.term);
|
||||
var term = terms.pop();
|
||||
if (term != '')
|
||||
$.get(searchInput.attr('data-autocomplete-url') + '?json', {filter: term + ' order:popularity,desc'}, function(data)
|
||||
{
|
||||
response($.map(data.tags, function(tag) { return { label: tag.name + ' (' + tag.count + ')', value: tag.name }; }));
|
||||
});
|
||||
retrieveTags(term, response);
|
||||
},
|
||||
focus: function(e)
|
||||
{
|
||||
@ -230,47 +281,144 @@ $(function()
|
||||
});
|
||||
});
|
||||
|
||||
function getTagItOptions()
|
||||
function attachTagIt(target)
|
||||
{
|
||||
return {
|
||||
var tagItOptions =
|
||||
{
|
||||
caseSensitive: false,
|
||||
onTagClicked: function(e, ui)
|
||||
{
|
||||
var targetTagit = ui.tag.parents('.tagit');
|
||||
var context = target.tagit('assignedTags');
|
||||
var options =
|
||||
{
|
||||
url: '/tags-related',
|
||||
data:
|
||||
{
|
||||
context: context,
|
||||
tag: ui.tagLabel
|
||||
}
|
||||
};
|
||||
|
||||
if (targetTagit.siblings('.related-tags:eq(0)').data('for') == options.data.tag)
|
||||
{
|
||||
targetTagit.siblings('.related-tags').slideUp(function()
|
||||
{
|
||||
$(this).remove();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
getJSON(options).success(function(data)
|
||||
{
|
||||
var list = $('<ul>');
|
||||
$.each(data.tags, function(i, tag)
|
||||
{
|
||||
var link = $('<a>');
|
||||
link.attr('href', tag['search-link']);
|
||||
link.text('#' + tag.name);
|
||||
link.click(function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
target.tagit('createTag', tag.name);
|
||||
});
|
||||
list.append(link.wrap('<li/>').parent());
|
||||
});
|
||||
targetTagit.siblings('.related-tags').slideUp(function()
|
||||
{
|
||||
$(this).remove();
|
||||
});
|
||||
var div = $('<div>');
|
||||
div.data('for', options.tag);
|
||||
div.addClass('related-tags');
|
||||
div.append('<p>Related tags:</p>');
|
||||
div.append(list);
|
||||
div.append('<div class="clear"></div>');
|
||||
div.insertAfter(targetTagit).hide().slideDown();
|
||||
});
|
||||
},
|
||||
|
||||
autocomplete:
|
||||
{
|
||||
source:
|
||||
function(request, response)
|
||||
{
|
||||
var term = request.term.toLowerCase();
|
||||
var tags = $.map(this.options.availableTags, function(a)
|
||||
var tagit = this;
|
||||
//var context = tagit.element.tagit('assignedTags');
|
||||
retrieveTags(request.term.toLowerCase(), function(tags)
|
||||
{
|
||||
return a.name;
|
||||
if (!tagit.options.allowDuplicates)
|
||||
{
|
||||
tags = $.grep(tags, function(tag)
|
||||
{
|
||||
return tagit.assignedTags().indexOf(tag.value) == -1;
|
||||
});
|
||||
}
|
||||
response(tags);
|
||||
});
|
||||
var results = $.grep(tags, function(a)
|
||||
{
|
||||
if (term.length < 3)
|
||||
return a.toLowerCase().indexOf(term) == 0;
|
||||
else
|
||||
return a.toLowerCase().indexOf(term) != -1;
|
||||
});
|
||||
results = results.slice(0, 15);
|
||||
if (!this.options.allowDuplicates)
|
||||
results = this._subtractArray(results, this.assignedTags());
|
||||
response(results);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
tagItOptions.placeholderText = target.attr('placeholder');
|
||||
target.tagit(tagItOptions);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//prevent keybindings from executing when flash posts are focused
|
||||
var oldMousetrapBind = Mousetrap.bind;
|
||||
Mousetrap.bind = function(key, func, args)
|
||||
{
|
||||
oldMousetrapBind(key, function()
|
||||
{
|
||||
if ($(document.activeElement).parents('.post-type-flash').length > 0)
|
||||
return false;
|
||||
|
||||
func();
|
||||
}, args);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//hotkeys
|
||||
$(function()
|
||||
{
|
||||
Mousetrap.bind('q', function() { $('#top-nav input').focus(); return false; }, 'keyup');
|
||||
Mousetrap.bind('w', function() { $('body,html').animate({scrollTop: '-=150px'}, 200); });
|
||||
Mousetrap.bind('s', function() { $('body,html').animate({scrollTop: '+=150px'}, 200); });
|
||||
Mousetrap.bind('a', function() { var url = $('.paginator:visible .prev:not(.disabled) a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
||||
Mousetrap.bind('d', function() { var url = $('.paginator:visible .next:not(.disabled) a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
||||
Mousetrap.bind('p', function() { $('.post a').eq(0).focus(); return false; }, 'keyup');
|
||||
Mousetrap.bind('q', function()
|
||||
{
|
||||
$('#top-nav input').focus();
|
||||
return false;
|
||||
}, 'keyup');
|
||||
|
||||
Mousetrap.bind('w', function()
|
||||
{
|
||||
$('body,html').animate({scrollTop: '-=150px'}, 200);
|
||||
});
|
||||
|
||||
Mousetrap.bind('s', function()
|
||||
{
|
||||
$('body,html').animate({scrollTop: '+=150px'}, 200);
|
||||
});
|
||||
|
||||
Mousetrap.bind('a', function()
|
||||
{
|
||||
var url = $('.paginator:visible .prev:not(.disabled) a').attr('href');
|
||||
if (typeof url !== 'undefined')
|
||||
window.location.href = url;
|
||||
}, 'keyup');
|
||||
|
||||
Mousetrap.bind('d', function()
|
||||
{
|
||||
var url = $('.paginator:visible .next:not(.disabled) a').attr('href');
|
||||
if (typeof url !== 'undefined')
|
||||
window.location.href = url;
|
||||
}, 'keyup');
|
||||
|
||||
Mousetrap.bind('p', function()
|
||||
{
|
||||
$('.post a').eq(0).focus();
|
||||
return false;
|
||||
}, 'keyup');
|
||||
});
|
||||
|
||||
|
||||
@ -279,7 +427,7 @@ function enableExitConfirmation()
|
||||
{
|
||||
$(window).bind('beforeunload', function(e)
|
||||
{
|
||||
return true;
|
||||
return 'There are unsaved changes.';
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
function scrolled()
|
||||
{
|
||||
var margin = 150;
|
||||
if ($(document).height() <= $(window).scrollTop() + $(window).height() + margin)
|
||||
var target = $('.paginator-content:eq(0)');
|
||||
var y = $(window).scrollTop() + $(window).height();
|
||||
var maxY = target.height() + target.position().top;
|
||||
if (y >= maxY - margin)
|
||||
{
|
||||
var pageNext = $(document).data('page-next');
|
||||
var pageDone = $(document).data('page-done');
|
||||
@ -12,12 +15,18 @@ function scrolled()
|
||||
if (pageNext != null && pageNext != pageDone)
|
||||
{
|
||||
$(document).data('page-done', pageNext);
|
||||
$.get(pageNext, [], function(response)
|
||||
getHtml(pageNext).success(function(response)
|
||||
{
|
||||
var dom = $(response);
|
||||
var nextPage = dom.find('.paginator .next:not(.disabled) a').attr('href');
|
||||
$(document).data('page-next', nextPage);
|
||||
$('.paginator-content').append($(response).find('.paginator-content').children().css({opacity: 0}).animate({opacity: 1}, 'slow'));
|
||||
|
||||
var source = $(response).find('.paginator-content');
|
||||
target.append(source
|
||||
.children()
|
||||
.css({opacity: 0})
|
||||
.animate({opacity: 1}, 'slow'));
|
||||
|
||||
$('body').trigger('dom-update');
|
||||
scrolled();
|
||||
});
|
||||
|
@ -1,41 +1,62 @@
|
||||
function bindToggleTag()
|
||||
{
|
||||
$('.post a.toggle-tag').bindOnce('toggle-tag', 'click', toggleTagEventHandler);
|
||||
}
|
||||
|
||||
function toggleTagEventHandler(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var aDom = $(this);
|
||||
if (aDom.hasClass('inactive'))
|
||||
return;
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var enable = !aDom.parents('.post').hasClass('tagged');
|
||||
var url = $(this).attr('href');
|
||||
url = url.replace(/\/[01]\/?$/, '/' + (enable ? '1' : '0'));
|
||||
postJSON({ url: url }).success(function(data)
|
||||
{
|
||||
aDom.removeClass('inactive');
|
||||
aDom.parents('.post').removeClass('tagged');
|
||||
if (enable)
|
||||
aDom.parents('.post').addClass('tagged');
|
||||
aDom.text(enable
|
||||
? aDom.attr('data-text-tagged')
|
||||
: aDom.attr('data-text-untagged'));
|
||||
}).error(function(xhr)
|
||||
{
|
||||
alert(xhr.responseJSON
|
||||
? xhr.responseJSON.message
|
||||
: 'Fatal error');
|
||||
aDom.removeClass('inactive');
|
||||
});
|
||||
}
|
||||
|
||||
function alignPosts()
|
||||
{
|
||||
var thumbnailWidth = $('#settings').attr('data-thumbnail-width');
|
||||
var thumbnailHeight = $('#settings').attr('data-thumbnail-width');
|
||||
var samplePost = $('.posts .post:last-child');
|
||||
var container = $('.posts');
|
||||
samplePost.find('.thumb').css('width', thumbnailWidth + 'px');
|
||||
var containerWidth = container.width();
|
||||
var thumbnailOuterWidth = samplePost.outerWidth(true);
|
||||
var thumbnailInnerWidth = samplePost.find('.thumb').outerWidth();
|
||||
var margin = thumbnailOuterWidth - thumbnailInnerWidth;
|
||||
var numberOfThumbnailsToFitInRow = Math.ceil(containerWidth / thumbnailOuterWidth);
|
||||
var newThumbnailWidth = Math.floor(containerWidth / numberOfThumbnailsToFitInRow) - margin;
|
||||
var newThumbnailHeight = newThumbnailWidth * thumbnailHeight / thumbnailWidth;
|
||||
container.find('.thumb').css({
|
||||
width: newThumbnailWidth + 'px',
|
||||
height: newThumbnailHeight + 'px'});
|
||||
}
|
||||
|
||||
$(function()
|
||||
{
|
||||
$('body').bind('dom-update', function()
|
||||
{
|
||||
$('.post a.toggle-tag').click(function(e)
|
||||
{
|
||||
if(e.isPropagationStopped())
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var aDom = $(this);
|
||||
if (aDom.hasClass('inactive'))
|
||||
return;
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var enable = !aDom.parents('.post').hasClass('tagged');
|
||||
var url = $(this).attr('href') + '?json';
|
||||
url = url.replace('_enable_', enable ? '1' : '0');
|
||||
$.get(url, {submit: 1}).always(function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
{
|
||||
aDom.removeClass('inactive');
|
||||
aDom.parents('.post').removeClass('tagged');
|
||||
if (enable)
|
||||
aDom.parents('.post').addClass('tagged');
|
||||
aDom.text(enable
|
||||
? aDom.attr('data-text-tagged')
|
||||
: aDom.attr('data-text-untagged'));
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['message'] ? data['message'] : 'Fatal error');
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
});
|
||||
bindToggleTag();
|
||||
alignPosts();
|
||||
});
|
||||
});
|
||||
|
@ -1,21 +1,21 @@
|
||||
$(function()
|
||||
var localPostId = 0;
|
||||
|
||||
function Post()
|
||||
{
|
||||
$('.tabs a').click(function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var className = $(this).parents('li').attr('class').replace('selected', '').replace(/^\s+|\s+$/, '');
|
||||
$('.tabs li').removeClass('selected');
|
||||
$(this).parents('li').addClass('selected');
|
||||
$('.tab').hide();
|
||||
$('.tab.' + className).show();
|
||||
});
|
||||
|
||||
var tags = [];
|
||||
$.getJSON('/tags?json', {filter: 'order:popularity,desc'}, function(data)
|
||||
{
|
||||
tags = data['tags'];
|
||||
});
|
||||
var post = this;
|
||||
this.id = ++localPostId;
|
||||
this.url = '';
|
||||
this.file = null;
|
||||
this.fileName = '';
|
||||
this.safety = 1;
|
||||
this.source = '';
|
||||
this.tags = [];
|
||||
this.anonymous = false;
|
||||
this.thumbnail = null;
|
||||
}
|
||||
|
||||
function bindFileHandlerEvents()
|
||||
{
|
||||
$('#file-handler').on('dragenter', function(e)
|
||||
{
|
||||
$(this).addClass('active');
|
||||
@ -28,7 +28,7 @@ $(function()
|
||||
}).on('drop', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
handleFiles(e.originalEvent.dataTransfer.files);
|
||||
addFiles(e.originalEvent.dataTransfer.files);
|
||||
$(this).trigger('dragleave');
|
||||
}).on('click', function(e)
|
||||
{
|
||||
@ -37,126 +37,653 @@ $(function()
|
||||
|
||||
$(':file').change(function(e)
|
||||
{
|
||||
handleFiles(this.files);
|
||||
addFiles(this.files);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function bindUrlHandlerEvents()
|
||||
{
|
||||
$('#url-handler-wrapper input').keydown(function(e)
|
||||
{
|
||||
if (e.which == 13)
|
||||
{
|
||||
$('#url-handler-wrapper button').trigger('click');
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
$('#url-handler-wrapper button').click(function(e)
|
||||
{
|
||||
var urls = [];
|
||||
$.each($('#url-handler-wrapper textarea').val().split(/\s+/), function(i, url)
|
||||
var url = $('#url-handler-wrapper input').val();
|
||||
url = url.replace(/^\s+|\s+$/, '');
|
||||
if (url == '')
|
||||
return;
|
||||
protocol = /^(\w+):\/\//.exec(url)
|
||||
if (!protocol)
|
||||
url = 'http://' + url;
|
||||
else
|
||||
{
|
||||
url = url.replace(/^\s+|\s+$/, '');
|
||||
if (url == '')
|
||||
protocol = protocol[1].toLowerCase();
|
||||
if (protocol != 'http' && protocol != 'https')
|
||||
{
|
||||
alert('Unsupported protocol: ' + protocol);
|
||||
return;
|
||||
urls.push(url);
|
||||
});
|
||||
$('#url-handler-wrapper textarea').val('');
|
||||
handleURLs(urls);
|
||||
}
|
||||
}
|
||||
$('#url-handler-wrapper input').val('');
|
||||
addURLs([url]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
$('.post .move-down-trigger, .post .move-up-trigger').on('click', function()
|
||||
function bindPostTableOperations()
|
||||
{
|
||||
Mousetrap.bind('a', function()
|
||||
{
|
||||
if ($('#the-submit').hasClass('inactive'))
|
||||
return;
|
||||
var dir = $(this).hasClass('move-down-trigger') ? 'd' : 'u';
|
||||
var post = $(this).parents('.post');
|
||||
if (dir == 'u')
|
||||
post.insertBefore(post.prev('.post'));
|
||||
else
|
||||
post.insertAfter(post.next('.post'));
|
||||
var prevPost = $('#posts tbody tr.selected:eq(0)').prev().data('post');
|
||||
if (prevPost)
|
||||
selectPostTableRow(prevPost);
|
||||
}, 'keyup');
|
||||
|
||||
Mousetrap.bind('d', function()
|
||||
{
|
||||
var nextPost = $('#posts tbody tr.selected:eq(0)').next().data('post');
|
||||
if (nextPost)
|
||||
selectPostTableRow(nextPost);
|
||||
}, 'keyup');
|
||||
|
||||
$('#upload-step2').find('.remove').click(function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
removePosts(getSelectedPosts());
|
||||
});
|
||||
$('.post .remove-trigger').on('click', function()
|
||||
$('#upload-step2').find('.move-up').click(function(e)
|
||||
{
|
||||
if ($('#the-submit').hasClass('inactive'))
|
||||
e.preventDefault();
|
||||
movePostsUp(getSelectedPosts());
|
||||
});
|
||||
$('#upload-step2').find('.move-down').click(function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
movePostsDown(getSelectedPosts());
|
||||
});
|
||||
}
|
||||
|
||||
function bindPostTableRowLightboxEvents(postTableRow)
|
||||
{
|
||||
var img = $(postTableRow).find('img');
|
||||
img.unbind('mouseenter').bind('mouseenter', function(e)
|
||||
{
|
||||
if (!img.attr('src'))
|
||||
return;
|
||||
$(this).parents('.post').slideUp(function()
|
||||
|
||||
$('#lightbox img').attr('src', $(this).attr('src'));
|
||||
$('#lightbox')
|
||||
.show()
|
||||
.position({
|
||||
of: $(this),
|
||||
my: 'left+10 center',
|
||||
at: 'right center',
|
||||
});
|
||||
});
|
||||
img.bind('mouseleave', function(e)
|
||||
{
|
||||
$('#lightbox').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function bindPostTableRowSelectEvent(tableRow)
|
||||
{
|
||||
tableRow.find('td.checkbox').click(function(e)
|
||||
{
|
||||
if (e.target.nodeName == 'TD')
|
||||
{
|
||||
$(this).remove();
|
||||
handleInputs([]);
|
||||
});
|
||||
var checkbox = $(this).find('input[type=checkbox]');
|
||||
checkbox.prop('checked', !checkbox.prop('checked'));
|
||||
}
|
||||
postTableCheckboxesChangedEventHandler();
|
||||
});
|
||||
}
|
||||
|
||||
function bindSelectAllEvent()
|
||||
{
|
||||
$('#posts thead th.checkbox').click(function(e)
|
||||
{
|
||||
var checkbox = $(this).find('input[type=checkbox]');
|
||||
if (e.target.nodeName == 'TH')
|
||||
checkbox.prop('checked', !checkbox.prop('checked'));
|
||||
$('#posts tbody input[type=checkbox]').prop('checked', checkbox.prop('checked'));
|
||||
postTableCheckboxesChangedEventHandler();
|
||||
});
|
||||
}
|
||||
|
||||
function bindPostTagChangeEvents(form, posts)
|
||||
{
|
||||
form.find('[name=tags]').tagit(
|
||||
{
|
||||
beforeTagAdded: function(e, ui) { addTagToPosts(posts, ui.tagLabel); },
|
||||
beforeTagRemoved: function(e, ui) { removeTagFromPosts(posts, ui.tagLabel); }
|
||||
});
|
||||
}
|
||||
|
||||
function bindPostAnonymityChangeEvent(form, posts)
|
||||
{
|
||||
form.find('[name=anonymous]').unbind('change').bind('change', function(e)
|
||||
{
|
||||
setPostsAnonymity(posts, $(e.target).is(':checked'));
|
||||
});
|
||||
}
|
||||
|
||||
function bindPostSafetyChangeEvent(form, posts)
|
||||
{
|
||||
form.find('[name=safety]').unbind('change').bind('change', function(e)
|
||||
{
|
||||
changePostsSafety(posts, $(this).val());
|
||||
});
|
||||
}
|
||||
|
||||
function bindPostSourceChangeEvent(form, posts)
|
||||
{
|
||||
form.find('[name=source]').unbind('change').bind('change', function(e)
|
||||
{
|
||||
changePostsSource(posts, $(this).val());
|
||||
});
|
||||
}
|
||||
|
||||
function addFiles(files)
|
||||
{
|
||||
var posts = [];
|
||||
$.each(files, function(i, file)
|
||||
{
|
||||
var post = new Post();
|
||||
post.file = file;
|
||||
post.fileName = file.name;
|
||||
|
||||
if (file.type.match('image.*'))
|
||||
{
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e)
|
||||
{
|
||||
post.thumbnail = e.target.result;
|
||||
updateThumbInForm(post);
|
||||
updatePostTableRow(post);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
posts.push(post);
|
||||
});
|
||||
|
||||
createTableRowsForPosts(posts);
|
||||
}
|
||||
|
||||
function updateThumbInForm(post)
|
||||
{
|
||||
var selectedPosts = getSelectedPosts();
|
||||
if (selectedPosts.length == 1 && selectedPosts[0] == post && post.thumbnail != null)
|
||||
$('#post-edit-form img')[0].setAttribute('src', post.thumbnail);
|
||||
}
|
||||
|
||||
function sendNextPost()
|
||||
function addURLs(urls)
|
||||
{
|
||||
var posts = [];
|
||||
$.each(urls, function(i, url)
|
||||
{
|
||||
var posts = $('#upload-step2 .post');
|
||||
if (posts.length == 0)
|
||||
post = new Post();
|
||||
post.url = url;
|
||||
post.fileName = url;
|
||||
post.source = url;
|
||||
|
||||
if (matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/))
|
||||
{
|
||||
uploadFinished();
|
||||
return;
|
||||
var realUrl = 'http://img.youtube.com/vi/' + matches[1] + '/mqdefault.jpg';
|
||||
post.thumbnail = realUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
post.thumbnail = '/posts/upload/thumb/' + btoa(url);
|
||||
}
|
||||
|
||||
var postDom = posts.first();
|
||||
var url = postDom.find('form').attr('action') + '?json';
|
||||
console.log(postDom.find('form').get(0));
|
||||
var fd = new FormData(postDom.find('form').get(0));
|
||||
posts.push(post);
|
||||
});
|
||||
|
||||
fd.append('file', postDom.data('file'));
|
||||
fd.append('url', postDom.data('url'));
|
||||
createTableRowsForPosts(posts);
|
||||
}
|
||||
|
||||
function createTableRowsForPosts(posts)
|
||||
{
|
||||
$.each(posts, function(i, post)
|
||||
{
|
||||
var tableRow = $('#posts .template').clone(true);
|
||||
tableRow.removeClass('template');
|
||||
tableRow.find('td:not(.checkbox)').click(postTableRowClickEventHandler);
|
||||
bindPostTableRowSelectEvent(tableRow);
|
||||
bindPostTableRowLightboxEvents(tableRow);
|
||||
tableRow.data('post', post);
|
||||
tableRow.data('post-id', post.id);
|
||||
$('#posts tbody').append(tableRow);
|
||||
updatePostTableRow(post);
|
||||
});
|
||||
|
||||
var ajaxData =
|
||||
{
|
||||
url: url,
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
success: function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
{
|
||||
postDom.slideUp(function()
|
||||
{
|
||||
postDom.remove();
|
||||
sendNextPost();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
postDom.find('.alert').html(data['messageHtml']).slideDown();
|
||||
enableUpload();
|
||||
}
|
||||
},
|
||||
error: function(data)
|
||||
{
|
||||
postDom.find('.alert').html('Fatal error').slideDown();
|
||||
enableUpload();
|
||||
}
|
||||
};
|
||||
selectPostTableRow(posts[0]);
|
||||
updateSelectAllState();
|
||||
showOrHidePostsTable();
|
||||
}
|
||||
|
||||
$.ajax(ajaxData);
|
||||
}
|
||||
|
||||
function uploadFinished()
|
||||
function showOrHidePostsTable()
|
||||
{
|
||||
var numberOfPosts = $('#posts tbody tr').length;
|
||||
if (numberOfPosts == 0)
|
||||
{
|
||||
disableExitConfirmation();
|
||||
window.location.href = $('#upload-step2').attr('data-redirect-url');
|
||||
$('#upload-step2').fadeOut();
|
||||
}
|
||||
else
|
||||
{
|
||||
enableExitConfirmation();
|
||||
$('#upload-step2').fadeIn();
|
||||
$('#posts-wrapper').show();
|
||||
/*if (numberOfPosts == 1)
|
||||
{
|
||||
$('#hybrid-view').append($('#the-submit-wrapper'));
|
||||
$('#posts-wrapper').hide('slide', {direction: 'left'});
|
||||
selectPostTableRow($('#posts tbody tr').eq(0).data('post'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#posts-wrapper').append($('#the-submit-wrapper'));
|
||||
$('#posts-wrapper').show('slide', {direction: 'right'});
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
function removePosts(posts)
|
||||
{
|
||||
var postTableRows = getPostTableRows(posts);
|
||||
$.each(postTableRows, function(i, postTableRow)
|
||||
{
|
||||
postTableRow.remove();
|
||||
});
|
||||
showOrHidePostsTable();
|
||||
postTableCheckboxesChangedEventHandler();
|
||||
}
|
||||
|
||||
function movePostsUp(posts)
|
||||
{
|
||||
var postTableRows = getPostTableRows(posts);
|
||||
$.each(postTableRows, function(i, postTableRow)
|
||||
{
|
||||
var postTableRow = $(postTableRow);
|
||||
postTableRow.insertBefore(postTableRow.prev('tr:not(.selected)'));
|
||||
});
|
||||
}
|
||||
|
||||
function movePostsDown(posts)
|
||||
{
|
||||
var postTableRows = getPostTableRows(posts).reverse();
|
||||
$.each(postTableRows, function(i, postTableRow)
|
||||
{
|
||||
var postTableRow = $(postTableRow);
|
||||
postTableRow.insertAfter(postTableRow.next('tr:not(.selected)'));
|
||||
});
|
||||
}
|
||||
|
||||
function selectPostTableRow(post)
|
||||
{
|
||||
$('#posts tbody input[type=checkbox]').prop('checked', false);
|
||||
$('#posts tbody tr').each(function(i, postTableRow)
|
||||
{
|
||||
if (post == $(postTableRow).data('post'))
|
||||
{
|
||||
$(this).find('input[type=checkbox]').prop('checked', true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
postTableCheckboxesChangedEventHandler();
|
||||
}
|
||||
|
||||
function postTableRowClickEventHandler(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var allCheckboxes = $(this).parents('table').find('tbody input[type=checkbox]');
|
||||
var myCheckbox = $(this).parents('tr').find('input[type=checkbox]');
|
||||
allCheckboxes.prop('checked', false);
|
||||
myCheckbox.prop('checked', true);
|
||||
postTableCheckboxesChangedEventHandler();
|
||||
}
|
||||
|
||||
function updateSelectAllState()
|
||||
{
|
||||
var numberOfAllPosts = $('#posts tbody tr').length;
|
||||
var numberOfSelectedPosts = $('#posts tbody tr.selected').length;
|
||||
$('#posts [name=select-all]').prop('checked', numberOfSelectedPosts == numberOfAllPosts);
|
||||
}
|
||||
|
||||
function postTableCheckboxesChangedEventHandler(e)
|
||||
{
|
||||
if ($('#posts').hasClass('disabled'))
|
||||
{
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
function disableUpload()
|
||||
$('#posts tbody tr').each(function(i, postRow)
|
||||
{
|
||||
var theSubmit = $('#the-submit');
|
||||
theSubmit.addClass('inactive');
|
||||
var posts = $('#upload-step2 .post');
|
||||
posts.find(':input').attr('readonly', true);
|
||||
posts.addClass('inactive');
|
||||
var checked = $(this).find('input[type=checkbox]').prop('checked');
|
||||
$(postRow).toggleClass('selected', checked);
|
||||
});
|
||||
|
||||
var allPosts = getAllPendingPosts();
|
||||
var selectedPosts = getSelectedPosts();
|
||||
updateSelectAllState();
|
||||
|
||||
if (selectedPosts.length == 0)
|
||||
hideForm();
|
||||
else
|
||||
showFormForPosts(selectedPosts);
|
||||
}
|
||||
|
||||
function getPostIds(posts)
|
||||
{
|
||||
var postIds = [];
|
||||
for (var i = 0; i < posts.length; i ++)
|
||||
postIds.push(posts[i].id);
|
||||
return postIds;
|
||||
}
|
||||
|
||||
function getPostTableRows(posts)
|
||||
{
|
||||
var postTableRows = [];
|
||||
var postIds = getPostIds(posts);
|
||||
$('#posts tbody tr').each(function(i, postTableRow)
|
||||
{
|
||||
var postId = $(postTableRow).data('post-id');
|
||||
if (postIds.indexOf(postId) != -1)
|
||||
postTableRows.push(postTableRow);
|
||||
});
|
||||
return postTableRows;
|
||||
}
|
||||
|
||||
function getAllPendingPosts()
|
||||
{
|
||||
var posts = [];
|
||||
$('#posts tbody tr').each(function(i, postTableRow)
|
||||
{
|
||||
posts.push($(postTableRow).data('post'));
|
||||
});
|
||||
return posts;
|
||||
}
|
||||
|
||||
function getSelectedPosts()
|
||||
{
|
||||
var posts = [];
|
||||
$('#posts tbody tr.selected').each(function(i, postTableRow)
|
||||
{
|
||||
posts.push($(postTableRow).data('post'));
|
||||
});
|
||||
return posts;
|
||||
}
|
||||
|
||||
function updatePostTableRow(post)
|
||||
{
|
||||
var safetyDescriptions =
|
||||
{
|
||||
1: 'safe',
|
||||
2: 'sketchy',
|
||||
3: 'unsafe'
|
||||
};
|
||||
var postTableRow = $(getPostTableRows([post])[0]);
|
||||
postTableRow.find('.tags').text(post.tags.join(', ') || '-');
|
||||
postTableRow.find('.safety div').attr('class', 'safety-' + safetyDescriptions[post.safety]);
|
||||
postTableRow.find('img').css('background-image', 'none')
|
||||
if (postTableRow.find('img').attr('src') != post.thumbnail && post.thumbnail != null) //huge speedup
|
||||
postTableRow.find('img')[0].setAttribute('src', post.thumbnail);
|
||||
}
|
||||
|
||||
function hideForm()
|
||||
{
|
||||
$('#post-edit-form').slideUp(function()
|
||||
{
|
||||
$('#post-edit-form .thumbnail').hide();
|
||||
$('#post-edit-form .source').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function showFormForPosts(posts)
|
||||
{
|
||||
var form = $('#post-edit-form');
|
||||
|
||||
form.slideDown();
|
||||
if (posts.length != 1)
|
||||
{
|
||||
form.find('.source').slideUp();
|
||||
form.find('.file-name strong').text('Multiple posts selected');
|
||||
form.find('.thumbnail').slideUp();
|
||||
}
|
||||
else
|
||||
{
|
||||
var post = posts[0];
|
||||
form.find('.source').slideDown();
|
||||
form.find('[name=source]').val(post.source);
|
||||
form.find('.file-name strong').text(post.fileName);
|
||||
form.find('.thumbnail').slideDown();
|
||||
if (post.thumbnail != null)
|
||||
{
|
||||
form.find('img').css('background-mage', 'none');
|
||||
form.find('img')[0].setAttribute('src', post.thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
function enableUpload()
|
||||
var commonAnonymity = getCommonPostAnonymity(posts);
|
||||
form.find('[name=anonymous]').prop('checked', commonAnonymity);
|
||||
|
||||
var commonSafety = getCommonPostSafety(posts);
|
||||
form.find('[name=safety]').prop('checked', false);
|
||||
if (commonSafety != 0)
|
||||
form.find('[name=safety][value=' + commonSafety + ']').prop('checked', true);
|
||||
|
||||
form.find('.related-tags').slideUp();
|
||||
form.find('[name=tags]').tagit(
|
||||
{
|
||||
var theSubmit = $('#the-submit');
|
||||
theSubmit.removeClass('inactive');
|
||||
var posts = $('#upload-step2 .post');
|
||||
posts.removeClass('inactive');
|
||||
posts.find(':input').attr('readonly', false);
|
||||
beforeTagAdded: function(e, ui) { },
|
||||
beforeTagRemoved: function(e, ui) { }
|
||||
});
|
||||
var commonTags = getCommonPostTags(posts);
|
||||
form.find('[name=tags]').tagit('removeAll');
|
||||
$.each(commonTags, function(i, tag)
|
||||
{
|
||||
form.find('[name=tags]').tagit('createTag', tag);
|
||||
});
|
||||
|
||||
bindPostSafetyChangeEvent(form, posts);
|
||||
bindPostSourceChangeEvent(form, posts);
|
||||
bindPostAnonymityChangeEvent(form, posts);
|
||||
bindPostTagChangeEvents(form, posts);
|
||||
}
|
||||
|
||||
function getCommonPostAnonymity(posts)
|
||||
{
|
||||
for (var i = 1; i < posts.length; i ++)
|
||||
if (posts[i].anonymous != posts[0].anonymous)
|
||||
return false;
|
||||
return posts[0].anonymous;
|
||||
}
|
||||
|
||||
function getCommonPostSafety(posts)
|
||||
{
|
||||
for (var i = 1; i < posts.length; i ++)
|
||||
if (posts[i].safety != posts[0].safety)
|
||||
return 0;
|
||||
return posts[0].safety;
|
||||
}
|
||||
|
||||
function getCommonPostTags(posts)
|
||||
{
|
||||
var commonTags = posts[0].tags;
|
||||
for (var i = 1; i < posts.length; i ++)
|
||||
{
|
||||
commonTags = commonTags.filter(function(tag)
|
||||
{
|
||||
return posts[i].tags.indexOf(tag) != -1;
|
||||
});
|
||||
}
|
||||
return commonTags;
|
||||
}
|
||||
|
||||
function changePostsSource(posts, newSource)
|
||||
{
|
||||
var maxLength = $('#post-edit-form input[name=source]').attr('maxlength');
|
||||
$.each(posts, function(i, post)
|
||||
{
|
||||
post.source = maxLength
|
||||
? newSource.substring(0, maxLength)
|
||||
: newSource;
|
||||
});
|
||||
}
|
||||
|
||||
function changePostsSafety(posts, newSafety)
|
||||
{
|
||||
$.each(posts, function(i, post)
|
||||
{
|
||||
post.safety = newSafety;
|
||||
updatePostTableRow(post);
|
||||
});
|
||||
}
|
||||
|
||||
function setPostsAnonymity(posts, newAnonymity)
|
||||
{
|
||||
$.each(posts, function(i, post)
|
||||
{
|
||||
post.anonymous = newAnonymity;
|
||||
updatePostTableRow(post);
|
||||
});
|
||||
}
|
||||
|
||||
function addTagToPosts(posts, tag)
|
||||
{
|
||||
$.each(posts, function(i, post)
|
||||
{
|
||||
var index = post.tags.indexOf(tag);
|
||||
if (index == -1)
|
||||
post.tags.push(tag);
|
||||
});
|
||||
$.each(posts, function(i, post)
|
||||
{
|
||||
updatePostTableRow(post);
|
||||
});
|
||||
}
|
||||
|
||||
function removeTagFromPosts(posts, tag)
|
||||
{
|
||||
$.each(posts, function(i, post)
|
||||
{
|
||||
var index = post.tags.indexOf(tag);
|
||||
if (index != -1)
|
||||
post.tags.splice(index, 1);
|
||||
});
|
||||
$.each(posts, function(i, post)
|
||||
{
|
||||
updatePostTableRow(post);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function enableOrDisableEditing(enabled)
|
||||
{
|
||||
var theSubmit = $('#the-submit');
|
||||
theSubmit.toggleClass('inactive', !enabled);
|
||||
var posts = $('#upload-step2 #posts');
|
||||
posts.toggleClass('inactive', !enabled);
|
||||
$('#post-edit-form input').prop('readonly', !enabled);
|
||||
}
|
||||
|
||||
function enableEditing()
|
||||
{
|
||||
enableOrDisableEditing(true);
|
||||
}
|
||||
|
||||
function disableEditing()
|
||||
{
|
||||
enableOrDisableEditing(false);
|
||||
}
|
||||
|
||||
function uploadFinished()
|
||||
{
|
||||
disableExitConfirmation();
|
||||
window.location.href = $('#upload-step2').attr('data-redirect-url');
|
||||
}
|
||||
|
||||
function stopUploadAndShowError(message)
|
||||
{
|
||||
$('#uploading-alert').slideUp();
|
||||
$('#upload-error-alert')
|
||||
.html(message)
|
||||
.slideDown();
|
||||
enableEditing();
|
||||
}
|
||||
|
||||
function sendNextPost()
|
||||
{
|
||||
$('#upload-error-alert').slideUp();
|
||||
|
||||
var posts = getAllPendingPosts();
|
||||
if (posts.length == 0)
|
||||
{
|
||||
uploadFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
var post = posts[0];
|
||||
var postTableRow = $('#posts tbody tr:first-child');
|
||||
var url = $('#the-submit-wrapper').find('form').attr('action');
|
||||
var fd = new FormData();
|
||||
|
||||
fd.append('file', post.file);
|
||||
fd.append('url', post.url);
|
||||
fd.append('source', post.source);
|
||||
fd.append('safety', post.safety);
|
||||
fd.append('anonymous', post.anonymous);
|
||||
fd.append('tags', post.tags.join(', '));
|
||||
|
||||
if (post.tags.length == 0)
|
||||
{
|
||||
stopUploadAndShowError('No tags set.');
|
||||
return;
|
||||
}
|
||||
|
||||
var ajaxData =
|
||||
{
|
||||
url: url,
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
dataType: 'json',
|
||||
success: function(data)
|
||||
{
|
||||
postTableRow.slideUp(function()
|
||||
{
|
||||
postTableRow.remove();
|
||||
sendNextPost();
|
||||
});
|
||||
},
|
||||
error: function(xhr)
|
||||
{
|
||||
stopUploadAndShowError(
|
||||
xhr.responseJSON
|
||||
? xhr.responseJSON.messageHtml
|
||||
: 'Fatal error');
|
||||
}
|
||||
};
|
||||
|
||||
postJSON(ajaxData);
|
||||
}
|
||||
|
||||
$(function()
|
||||
{
|
||||
bindFileHandlerEvents();
|
||||
bindUrlHandlerEvents();
|
||||
bindSelectAllEvent();
|
||||
bindPostTableOperations();
|
||||
attachTagIt($('input[name=tags]'));
|
||||
|
||||
$('#the-submit').click(function(e)
|
||||
{
|
||||
@ -164,86 +691,12 @@ $(function()
|
||||
var theSubmit = $(this);
|
||||
if (theSubmit.hasClass('inactive'))
|
||||
return;
|
||||
disableUpload();
|
||||
disableEditing();
|
||||
|
||||
$('#posts input[type=checkbox]').prop('checked', false);
|
||||
postTableCheckboxesChangedEventHandler();
|
||||
$('#uploading-alert').slideDown();
|
||||
|
||||
sendNextPost();
|
||||
});
|
||||
|
||||
function handleFiles(files)
|
||||
{
|
||||
handleInputs(files, function(postDom, file)
|
||||
{
|
||||
postDom.data('file', file);
|
||||
$('.file-name strong', postDom).text(file.name);
|
||||
|
||||
if (file.type.match('image.*'))
|
||||
{
|
||||
var img = postDom.find('img')
|
||||
var reader = new FileReader();
|
||||
reader.onload = (function(theFile, img)
|
||||
{
|
||||
return function(e)
|
||||
{
|
||||
img.css('background-image', 'none');
|
||||
img.attr('src', e.target.result);
|
||||
};
|
||||
})(file, img);
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleURLs(urls)
|
||||
{
|
||||
handleInputs(urls, function(postDom, url)
|
||||
{
|
||||
postDom.data('url', url);
|
||||
postDom.find('[name=source]').val(url);
|
||||
if (matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/))
|
||||
{
|
||||
postDom.find('.file-name strong').text(url);
|
||||
$.getJSON('http://gdata.youtube.com/feeds/api/videos/' + matches[1] + '?v=2&alt=jsonc', function(data)
|
||||
{
|
||||
postDom.find('.file-name strong')
|
||||
.text(data.data.title);
|
||||
postDom.find('img')
|
||||
.css('background-image', 'none')
|
||||
.attr('src', data.data.thumbnail.hqDefault);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
postDom.find('.file-name strong')
|
||||
.text(url);
|
||||
postDom.find('img')
|
||||
.css('background-image', 'none')
|
||||
.attr('src', url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleInputs(inputs, callback)
|
||||
{
|
||||
for (var i = 0; i < inputs.length; i ++)
|
||||
{
|
||||
enableExitConfirmation();
|
||||
var input = inputs[i];
|
||||
var postDom = $('#post-template').clone(true);
|
||||
postDom.find('form').submit(false);
|
||||
postDom.removeAttr('id');
|
||||
|
||||
$('.posts').append(postDom);
|
||||
|
||||
postDom.show();
|
||||
var tagItOptions = getTagItOptions();
|
||||
tagItOptions.availableTags = tags;
|
||||
tagItOptions.placeholderText = $('.tags input').attr('placeholder');
|
||||
$('.tags input', postDom).tagit(tagItOptions);
|
||||
|
||||
callback(postDom, input);
|
||||
}
|
||||
if ($('.posts .post').length == 0)
|
||||
$('#upload-step2').fadeOut();
|
||||
else
|
||||
$('#upload-step2').fadeIn();
|
||||
}
|
||||
});
|
||||
|
@ -1,8 +1,42 @@
|
||||
function constrainFlashSize()
|
||||
{
|
||||
var target = $('.post-type-flash object');
|
||||
var container = $('#inner-content');
|
||||
target.width('');
|
||||
if (target.width() > container.width())
|
||||
{
|
||||
target.width(container.width())
|
||||
target.height(container.width() * target.attr('height') / target.attr('width'));
|
||||
}
|
||||
}
|
||||
|
||||
$(function()
|
||||
{
|
||||
function onDomUpdate()
|
||||
{
|
||||
$('#sidebar .edit a').click(function(e)
|
||||
constrainFlashSize();
|
||||
|
||||
$('#sidebar .permalink').bindOnce('select-link', 'click', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var node = $(this)[0];
|
||||
if (document.body.createTextRange)
|
||||
{
|
||||
var range = document.body.createTextRange();
|
||||
range.moveToElementText(node);
|
||||
range.select();
|
||||
}
|
||||
else if (window.getSelection)
|
||||
{
|
||||
var selection = window.getSelection();
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(node);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
});
|
||||
|
||||
$('#sidebar a.edit-post').bindOnce('edit-post', 'click', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
@ -12,46 +46,62 @@ $(function()
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var formDom = $('form.edit-post');
|
||||
formDom.data('original-data', formDom.serialize());
|
||||
if (formDom.find('.tagit').length == 0)
|
||||
{
|
||||
$.getJSON('/tags?json', {filter: 'order:popularity,desc'}, function(data)
|
||||
attachTagIt($('input[name=tags]'));
|
||||
aDom.removeClass('inactive');
|
||||
|
||||
formDom.find('input[type=text]:visible:eq(0)').focus();
|
||||
formDom.find('textarea, input').bind('change keyup', function()
|
||||
{
|
||||
aDom.removeClass('inactive');
|
||||
var tags = data['tags'];
|
||||
|
||||
var tagItOptions = getTagItOptions();
|
||||
tagItOptions.availableTags = tags;
|
||||
tagItOptions.placeholderText = $('.tags input').attr('placeholder');
|
||||
$('.tags input').tagit(tagItOptions);
|
||||
|
||||
formDom.find('input[type=text]:visible:eq(0)').focus();
|
||||
formDom.find('textarea, input').bind('change keyup', function()
|
||||
{
|
||||
if (formDom.serialize() != formDom.data('original-data'))
|
||||
enableExitConfirmation();
|
||||
});
|
||||
if (formDom.serialize() != formDom.data('original-data'))
|
||||
enableExitConfirmation();
|
||||
});
|
||||
}
|
||||
else
|
||||
aDom.removeClass('inactive');
|
||||
|
||||
var editUnit = formDom.parents('.unit');
|
||||
var postUnit = $('.post-wrapper');
|
||||
if (!$(formDom).is(':visible'))
|
||||
{
|
||||
formDom.parents('.unit')
|
||||
.show().css('height', formDom.height()).hide()
|
||||
.slideDown(function()
|
||||
formDom.data('original-data', formDom.serialize());
|
||||
|
||||
editUnit.show();
|
||||
var editUnitHeight = formDom.height();
|
||||
editUnit.css('height', editUnitHeight);
|
||||
editUnit.hide();
|
||||
|
||||
if (postUnit.height() < editUnitHeight)
|
||||
postUnit.animate({height: editUnitHeight + 'px'}, 'fast');
|
||||
|
||||
editUnit.slideDown('fast', function()
|
||||
{
|
||||
$(this).css('height', 'auto');
|
||||
});
|
||||
}
|
||||
$('html, body').animate({ scrollTop: $(formDom).offset().top + 'px' }, 'fast');
|
||||
else
|
||||
{
|
||||
editUnit.slideUp('fast');
|
||||
|
||||
var postUnitOldHeight = postUnit.height();
|
||||
postUnit.height('auto');
|
||||
var postUnitHeight = postUnit.height();
|
||||
postUnit.height(postUnitOldHeight);
|
||||
if (postUnitHeight != postUnitOldHeight)
|
||||
postUnit.animate({height: postUnitHeight + 'px'});
|
||||
|
||||
if ($('.post-wrapper').height() < editUnitHeight)
|
||||
$('.post-wrapper').animate({height: editUnitHeight + 'px'});
|
||||
return;
|
||||
}
|
||||
|
||||
formDom.find('input[type=text]:visible:eq(0)').focus();
|
||||
});
|
||||
|
||||
$('.comments.unit a.simple-action').data('callback', function()
|
||||
{
|
||||
$.get(window.location.href, function(data)
|
||||
getHtml(window.location.href).success(function(data)
|
||||
{
|
||||
$('.comments-wrapper').replaceWith($(data).find('.comments-wrapper'));
|
||||
$('body').trigger('dom-update');
|
||||
@ -60,7 +110,7 @@ $(function()
|
||||
|
||||
$('#sidebar a.simple-action').data('callback', function()
|
||||
{
|
||||
$.get(window.location.href, function(data)
|
||||
getHtml(window.location.href).success(function(data)
|
||||
{
|
||||
$('#sidebar').replaceWith($(data).find('#sidebar'));
|
||||
$('body').trigger('dom-update');
|
||||
@ -73,7 +123,6 @@ $(function()
|
||||
$('form.edit-post').submit(function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
rememberLastSearchQuery();
|
||||
|
||||
var formDom = $(this);
|
||||
if (formDom.hasClass('inactive'))
|
||||
@ -81,7 +130,7 @@ $(function()
|
||||
formDom.addClass('inactive');
|
||||
formDom.find(':input').attr('readonly', true);
|
||||
|
||||
var url = formDom.attr('action') + '?json';
|
||||
var url = formDom.attr('action');
|
||||
var fd = new FormData(formDom[0]);
|
||||
|
||||
var ajaxData =
|
||||
@ -90,41 +139,59 @@ $(function()
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
|
||||
success: function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
{
|
||||
disableExitConfirmation();
|
||||
disableExitConfirmation();
|
||||
|
||||
$.get(window.location.href, function(data)
|
||||
{
|
||||
$('#sidebar').replaceWith($(data).find('#sidebar'));
|
||||
$('#edit-token').replaceWith($(data).find('#edit-token'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
formDom.parents('.unit').hide();
|
||||
}
|
||||
else
|
||||
getHtml(window.location.href).success(function(data)
|
||||
{
|
||||
alert(data['message']);
|
||||
}
|
||||
$('#sidebar').replaceWith($(data).find('#sidebar'));
|
||||
$('#revision').replaceWith($(data).find('#revision'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
formDom.parents('.unit').hide();
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
},
|
||||
error: function()
|
||||
error: function(xhr)
|
||||
{
|
||||
alert('Fatal error');
|
||||
alert(xhr.responseJSON
|
||||
? xhr.responseJSON.message
|
||||
: 'Fatal error');
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
};
|
||||
|
||||
$.ajax(ajaxData);
|
||||
postJSON(ajaxData);
|
||||
});
|
||||
|
||||
Mousetrap.bind('a', function() { var a = $('#sidebar .left a'); var url = a.attr('href'); if (typeof url !== 'undefined') { a.click(); window.location.href = url; } }, 'keyup');
|
||||
Mousetrap.bind('d', function() { var a = $('#sidebar .right a'); var url = a.attr('href'); if (typeof url !== 'undefined') { a.click(); window.location.href = url; } }, 'keyup');
|
||||
Mousetrap.bind('e', function() { $('li.edit a').trigger('click'); return false; }, 'keyup');
|
||||
Mousetrap.bind('a', function()
|
||||
{
|
||||
var a = $('#sidebar .left a');
|
||||
var url = a.attr('href');
|
||||
if (typeof url !== 'undefined')
|
||||
{
|
||||
a.click();
|
||||
window.location.href = url;
|
||||
}
|
||||
}, 'keyup');
|
||||
|
||||
Mousetrap.bind('d', function()
|
||||
{
|
||||
var a = $('#sidebar .right a');
|
||||
var url = a.attr('href');
|
||||
if (typeof url !== 'undefined')
|
||||
{
|
||||
a.click();
|
||||
window.location.href = url;
|
||||
}
|
||||
}, 'keyup');
|
||||
|
||||
Mousetrap.bind('e', function()
|
||||
{
|
||||
$('a.edit-post').trigger('click');
|
||||
return false;
|
||||
}, 'keyup');
|
||||
});
|
||||
|
11
public_html/media/js/user-edit.js
Normal file
@ -0,0 +1,11 @@
|
||||
$(function()
|
||||
{
|
||||
$('.avatar-content').parents('.form-row').hide();
|
||||
$('.avatar-style').click(function()
|
||||
{
|
||||
if ($(this).val() == '2'/*custom*/)
|
||||
{
|
||||
$('.avatar-content').parents('.form-row').show();
|
||||
}
|
||||
});
|
||||
});
|
3
public_html/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
User-Agent: *
|
||||
Allow: /$
|
||||
Disallow: /
|
2
public_html/thumbs/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
@ -1,26 +1,32 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
|
||||
function usage()
|
||||
{
|
||||
echo 'Usage: ' . basename(__FILE__);
|
||||
echo ' QUERY' . PHP_EOL;
|
||||
return true;
|
||||
}
|
||||
Access::disablePrivilegeChecking();
|
||||
|
||||
array_shift($argv);
|
||||
if (empty($argv))
|
||||
usage() and die;
|
||||
$query = join(' ', $argv);
|
||||
|
||||
$query = array_shift($argv);
|
||||
$posts = Model_Post::getEntities($query, null, null);
|
||||
$posts = PostSearchService::getEntities($query, null, null);
|
||||
foreach ($posts as $post)
|
||||
{
|
||||
echo implode("\t",
|
||||
$info =
|
||||
[
|
||||
$post->id,
|
||||
$post->name,
|
||||
Model_Post::getFullPath($post->name),
|
||||
$post->mimeType,
|
||||
]). PHP_EOL;
|
||||
$post->getId(),
|
||||
$post->getName(),
|
||||
$post->getType()->toDisplayString(),
|
||||
];
|
||||
|
||||
$additionalInfo = [];
|
||||
if ($post->getType()->toInteger() != PostType::Youtube)
|
||||
{
|
||||
$additionalInfo =
|
||||
[
|
||||
file_exists($post->getContentPath())
|
||||
? $post->getContentPath()
|
||||
: 'DOES NOT EXIST',
|
||||
$post->getMimeType(),
|
||||
];
|
||||
}
|
||||
|
||||
echo implode("\t", array_merge($info, $additionalInfo)) . PHP_EOL;
|
||||
}
|
||||
|
43
scripts/generate-thumbs.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
|
||||
Access::disablePrivilegeChecking();
|
||||
|
||||
$query = [];
|
||||
$force = false;
|
||||
|
||||
array_shift($argv);
|
||||
foreach ($argv as $arg)
|
||||
{
|
||||
if ($arg == '-f' or $arg == '--force')
|
||||
$force = true;
|
||||
else
|
||||
$query []= $arg;
|
||||
}
|
||||
$query = join(' ', $query);
|
||||
|
||||
$posts = PostSearchService::getEntities($query, null, null);
|
||||
$entityCount = PostSearchService::getEntityCount($query, null, null);
|
||||
$i = 0;
|
||||
foreach ($posts as $post)
|
||||
{
|
||||
++ $i;
|
||||
printf('%s (%d/%d)' . PHP_EOL, TextHelper::reprPost($post), $i, $entityCount);
|
||||
|
||||
if (file_exists($post->getThumbnailPath()) and $force)
|
||||
unlink($post->getThumbnailPath());
|
||||
|
||||
if (!file_exists($post->getThumbnailPath()))
|
||||
{
|
||||
try
|
||||
{
|
||||
$post->generateThumbnail();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
echo $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo 'Don\'t forget to check access rights.' . PHP_EOL;
|
@ -1,30 +1,36 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
|
||||
function usage()
|
||||
Access::disablePrivilegeChecking();
|
||||
|
||||
$usage = function()
|
||||
{
|
||||
echo 'Usage: ' . basename(__FILE__);
|
||||
echo ' -print|-purge|-move DIR' . PHP_EOL;
|
||||
echo 'Usage: ' . basename(__FILE__) . PHP_EOL;
|
||||
echo ' -p|--print OR' . PHP_EOL;
|
||||
echo ' -d|--delete OR' . PHP_EOL;
|
||||
echo ' -m|--move [TARGET]' . PHP_EOL;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
array_shift($argv);
|
||||
if (empty($argv))
|
||||
usage() and die;
|
||||
$usage() and die;
|
||||
|
||||
$action = array_shift($argv);
|
||||
switch ($action)
|
||||
{
|
||||
case '-print':
|
||||
case '-p':
|
||||
case '--print':
|
||||
$func = function($name)
|
||||
{
|
||||
echo $name . PHP_EOL;
|
||||
};
|
||||
break;
|
||||
|
||||
case '-move':
|
||||
case '-m':
|
||||
case '--move':
|
||||
if (empty($argv))
|
||||
usage() and die;
|
||||
$usage() and die;
|
||||
$dir = array_shift($argv);
|
||||
if (!file_exists($dir))
|
||||
mkdir($dir, 0755, true);
|
||||
@ -33,17 +39,18 @@ switch ($action)
|
||||
$func = function($name) use ($dir)
|
||||
{
|
||||
echo $name . PHP_EOL;
|
||||
$srcPath = Model_Post::getFullPath($name);
|
||||
$srcPath = Core::getConfig()->main->filesPath . DS . $name;
|
||||
$dstPath = $dir . DS . $name;
|
||||
rename($srcPath, $dstPath);
|
||||
};
|
||||
break;
|
||||
|
||||
case '-purge':
|
||||
case '-d':
|
||||
case '--delete':
|
||||
$func = function($name)
|
||||
{
|
||||
echo $name . PHP_EOL;
|
||||
$srcPath = Model_Post::getFullPath($name);
|
||||
$srcPath = Core::getConfig()->main->filesPath . DS . $name;
|
||||
unlink($srcPath);
|
||||
};
|
||||
break;
|
||||
@ -53,13 +60,13 @@ switch ($action)
|
||||
}
|
||||
|
||||
$names = [];
|
||||
foreach (R::findAll('post') as $post)
|
||||
foreach (PostModel::getAll() as $post)
|
||||
{
|
||||
$names []= $post->name;
|
||||
$names []= $post->getName();
|
||||
}
|
||||
$names = array_flip($names);
|
||||
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$config = Core::getConfig();
|
||||
foreach (glob(TextHelper::absolutePath($config->main->filesPath) . DS . '*') as $name)
|
||||
{
|
||||
$name = basename($name);
|
||||
|
@ -1,47 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
|
||||
function usage()
|
||||
{
|
||||
echo 'Usage: ' . basename(__FILE__);
|
||||
echo ' -print|-purge';
|
||||
return true;
|
||||
}
|
||||
|
||||
array_shift($argv);
|
||||
if (empty($argv))
|
||||
usage() and die;
|
||||
|
||||
function printUser($user)
|
||||
{
|
||||
echo 'ID: ' . $user->id . PHP_EOL;
|
||||
echo 'Name: ' . $user->name . PHP_EOL;
|
||||
echo 'E-mail: ' . $user->email_unconfirmed . PHP_EOL;
|
||||
echo 'Date joined: ' . date('Y-m-d H:i:s', $user->join_date) . PHP_EOL;
|
||||
echo PHP_EOL;
|
||||
}
|
||||
|
||||
$action = array_shift($argv);
|
||||
switch ($action)
|
||||
{
|
||||
case '-print':
|
||||
$func = 'printUser';
|
||||
break;
|
||||
|
||||
case '-purge':
|
||||
$func = function($user)
|
||||
{
|
||||
printUser($user);
|
||||
Model_User::remove($user);
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
die('Unknown action' . PHP_EOL);
|
||||
}
|
||||
|
||||
$rows = R::find('user', 'email_confirmed IS NULL AND DATETIME(join_date) < DATETIME("now", "-21 days")');
|
||||
foreach ($rows as $user)
|
||||
{
|
||||
$func($user);
|
||||
}
|
133
src/Access.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
class Access
|
||||
{
|
||||
private static $privileges;
|
||||
private static $checkPrivileges;
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$checkPrivileges = true;
|
||||
self::$privileges = \Chibi\Cache::getCache('privileges', [get_called_class(), 'getPrivilegesFromConfig']);
|
||||
}
|
||||
|
||||
public static function initWithoutCache()
|
||||
{
|
||||
self::$checkPrivileges = true;
|
||||
self::$privileges = self::getPrivilegesFromConfig();
|
||||
}
|
||||
|
||||
public static function getPrivilegesFromConfig()
|
||||
{
|
||||
$privileges = [];
|
||||
foreach (Core::getConfig()->privileges as $key => $minAccessRankName)
|
||||
{
|
||||
if (strpos($key, '.') === false)
|
||||
$key .= '.';
|
||||
list ($privilegeName, $subPrivilegeName) = explode('.', $key);
|
||||
$minAccessRank = new AccessRank(TextHelper::resolveConstant($minAccessRankName, 'AccessRank'));
|
||||
|
||||
if (!in_array($privilegeName, Privilege::getAllConstants()))
|
||||
throw new Exception('Invalid privilege name in config: ' . $privilegeName);
|
||||
|
||||
if (!isset($privileges[$privilegeName]))
|
||||
{
|
||||
$privileges[$privilegeName] = [];
|
||||
$privileges[$privilegeName][null] = $minAccessRank;
|
||||
}
|
||||
|
||||
$privileges[$privilegeName][$subPrivilegeName] = $minAccessRank;
|
||||
}
|
||||
return $privileges;
|
||||
}
|
||||
|
||||
public static function check(Privilege $privilege, $user = null)
|
||||
{
|
||||
if (!self::$checkPrivileges)
|
||||
return true;
|
||||
|
||||
if ($user === null)
|
||||
$user = Auth::getCurrentUser();
|
||||
|
||||
$minAccessRank = new AccessRank(AccessRank::Nobody);
|
||||
|
||||
if (isset(self::$privileges[$privilege->primary][$privilege->secondary]))
|
||||
$minAccessRank = self::$privileges[$privilege->primary][$privilege->secondary];
|
||||
|
||||
elseif (isset(self::$privileges[$privilege->primary][null]))
|
||||
$minAccessRank = self::$privileges[$privilege->primary][null];
|
||||
|
||||
return $user->getAccessRank()->toInteger() >= $minAccessRank->toInteger();
|
||||
}
|
||||
|
||||
public static function checkEmailConfirmation($user = null)
|
||||
{
|
||||
if (!self::$checkPrivileges)
|
||||
return true;
|
||||
|
||||
if ($user === null)
|
||||
$user = Auth::getCurrentUser();
|
||||
|
||||
if (!$user->getConfirmedEmail())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function assertAuthentication()
|
||||
{
|
||||
if (!Auth::isLoggedIn())
|
||||
self::fail('Not logged in');
|
||||
}
|
||||
|
||||
public static function assert(Privilege $privilege, $user = null)
|
||||
{
|
||||
if (!self::check($privilege, $user))
|
||||
self::fail('Insufficient privileges (' . $privilege->toDisplayString() . ')');
|
||||
}
|
||||
|
||||
public static function assertEmailConfirmation($user = null)
|
||||
{
|
||||
if (!self::checkEmailConfirmation($user))
|
||||
self::fail('Need e-mail address confirmation to continue');
|
||||
}
|
||||
|
||||
public static function fail($message)
|
||||
{
|
||||
throw new AccessException($message);
|
||||
}
|
||||
|
||||
public static function getIdentity($user)
|
||||
{
|
||||
if (!$user)
|
||||
return 'all';
|
||||
return $user->getId() == Auth::getCurrentUser()->getId() ? 'own' : 'all';
|
||||
}
|
||||
|
||||
public static function getAllowedSafety()
|
||||
{
|
||||
if (!self::$checkPrivileges)
|
||||
return PostSafety::getAll();
|
||||
|
||||
return array_filter(PostSafety::getAll(), function($safety)
|
||||
{
|
||||
return Access::check(new Privilege(Privilege::ListPosts, $safety->toString()))
|
||||
and Auth::getCurrentUser()->getSettings()->hasEnabledSafety($safety);
|
||||
});
|
||||
}
|
||||
|
||||
public static function getAllDefinedSubPrivileges($privilege)
|
||||
{
|
||||
if (!isset(self::$privileges[$privilege]))
|
||||
return null;
|
||||
return self::$privileges[$privilege];
|
||||
}
|
||||
|
||||
public static function disablePrivilegeChecking()
|
||||
{
|
||||
self::$checkPrivileges = false;
|
||||
}
|
||||
|
||||
public static function enablePrivilegeChecking()
|
||||
{
|
||||
self::$checkPrivileges = true;
|
||||
}
|
||||
}
|
4
src/AccessException.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
class AccessException extends SimpleException
|
||||
{
|
||||
}
|
117
src/Api/Api.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
final class Api
|
||||
{
|
||||
public static function getUrl()
|
||||
{
|
||||
return Core::getRouter()->linkTo(['ApiController', 'runAction']);
|
||||
}
|
||||
|
||||
public static function run(IJob $job, $jobArgs)
|
||||
{
|
||||
$user = Auth::getCurrentUser();
|
||||
|
||||
return Core::getDatabase()->transaction(function() use ($job, $jobArgs)
|
||||
{
|
||||
$job->setArguments($jobArgs);
|
||||
|
||||
self::checkArguments($job);
|
||||
|
||||
$job->prepare();
|
||||
|
||||
self::checkPrivileges($job);
|
||||
|
||||
return $job->execute();
|
||||
});
|
||||
}
|
||||
|
||||
public static function runMultiple($jobs)
|
||||
{
|
||||
$statuses = [];
|
||||
Core::getDatabase()->transaction(function() use ($jobs, &$statuses)
|
||||
{
|
||||
foreach ($jobs as $jobItem)
|
||||
{
|
||||
list ($job, $jobArgs) = $jobItem;
|
||||
$statuses []= self::run($job, $jobArgs);
|
||||
}
|
||||
});
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
public static function checkArguments(IJob $job)
|
||||
{
|
||||
self::runArgumentCheck($job, $job->getRequiredArguments());
|
||||
}
|
||||
|
||||
public static function checkPrivileges(IJob $job)
|
||||
{
|
||||
if ($job->isAuthenticationRequired())
|
||||
Access::assertAuthentication();
|
||||
|
||||
if ($job->isConfirmedEmailRequired())
|
||||
Access::assertEmailConfirmation();
|
||||
|
||||
$mainPrivilege = $job->getRequiredMainPrivilege();
|
||||
$subPrivileges = $job->getRequiredSubPrivileges();
|
||||
if (!is_array($subPrivileges))
|
||||
$subPrivileges = [$subPrivileges];
|
||||
|
||||
if ($mainPrivilege !== null)
|
||||
{
|
||||
Access::assert(new Privilege($mainPrivilege));
|
||||
foreach ($subPrivileges as $subPrivilege)
|
||||
Access::assert(new Privilege($mainPrivilege, $subPrivilege));
|
||||
}
|
||||
}
|
||||
|
||||
private static function runArgumentCheck(IJob $job, $item)
|
||||
{
|
||||
if (is_array($item))
|
||||
throw new Exception('Argument definition cannot be an array.');
|
||||
elseif ($item instanceof JobArgsNestedStruct)
|
||||
{
|
||||
if ($item instanceof JobArgsAlternative)
|
||||
{
|
||||
$success = false;
|
||||
foreach ($item->args as $subItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
self::runArgumentCheck($job, $subItem);
|
||||
$success = true;
|
||||
}
|
||||
catch (ApiJobUnsatisfiedException $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
if (!$success)
|
||||
throw new ApiJobUnsatisfiedException($job);
|
||||
}
|
||||
elseif ($item instanceof JobArgsConjunction)
|
||||
{
|
||||
foreach ($item->args as $subItem)
|
||||
!self::runArgumentCheck($job, $subItem);
|
||||
}
|
||||
}
|
||||
elseif ($item === null)
|
||||
return;
|
||||
elseif (!$job->hasArgument($item))
|
||||
throw new ApiJobUnsatisfiedException($job, $item);
|
||||
}
|
||||
|
||||
public static function getAllJobClassNames()
|
||||
{
|
||||
$pathToJobs = Core::getConfig()->rootDir . DS . 'src' . DS . 'Api' . DS . 'Jobs';
|
||||
$directory = new RecursiveDirectoryIterator($pathToJobs);
|
||||
$iterator = new RecursiveIteratorIterator($directory);
|
||||
$regex = new RegexIterator($iterator, '/^.+Job\.php$/i');
|
||||
$files = array_keys(iterator_to_array($regex));
|
||||
|
||||
\Chibi\Util\Reflection::loadClasses($files);
|
||||
return array_filter(get_declared_classes(), function($x)
|
||||
{
|
||||
$class = new ReflectionClass($x);
|
||||
return !$class->isAbstract() and $class->isSubClassOf('AbstractJob');
|
||||
});
|
||||
}
|
||||
}
|
30
src/Api/ApiFileInput.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* Used for serializing files passed in POST requests to job arguments
|
||||
*/
|
||||
class ApiFileInput
|
||||
{
|
||||
public $filePath;
|
||||
public $fileName;
|
||||
public $originalPath;
|
||||
|
||||
public function __construct($filePath, $fileName)
|
||||
{
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'upload') . '.dat';
|
||||
$this->originalPath = $tmpPath;
|
||||
|
||||
//php "security" bullshit
|
||||
if (is_uploaded_file($filePath))
|
||||
move_uploaded_file($filePath, $tmpPath);
|
||||
else
|
||||
copy($filePath, $tmpPath);
|
||||
|
||||
$this->filePath = $tmpPath;
|
||||
$this->fileName = $fileName;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
TransferHelper::remove($this->originalPath);
|
||||
}
|
||||
}
|
30
src/Api/ApiFileOutput.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* Used for serializing files output from jobs
|
||||
*/
|
||||
class ApiFileOutput implements ISerializable
|
||||
{
|
||||
public $fileContent;
|
||||
public $fileName;
|
||||
public $lastModified;
|
||||
public $mimeType;
|
||||
|
||||
public function __construct($filePath, $fileName)
|
||||
{
|
||||
$this->fileContent = file_get_contents($filePath);
|
||||
$this->fileName = $fileName;
|
||||
$this->lastModified = filemtime($filePath);
|
||||
$this->mimeType = mime_content_type($filePath);
|
||||
}
|
||||
|
||||
public function serializeToArray()
|
||||
{
|
||||
return
|
||||
[
|
||||
'name ' => $this->fileName,
|
||||
'modification-time' => $this->lastModified,
|
||||
'mime-type' => $this->mimeType,
|
||||
'content' => base64_encode(gzencode($this->fileContent)),
|
||||
];
|
||||
}
|
||||
}
|
10
src/Api/ApiJobUnsatisfiedException.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
class ApiJobUnsatisfiedException extends SimpleException
|
||||
{
|
||||
public function __construct(IJob $job, $arg = null)
|
||||
{
|
||||
parent::__construct('%s cannot be run due to unsatisfied execution conditions (%s).',
|
||||
get_class($job),
|
||||
$arg);
|
||||
}
|
||||
}
|
8
src/Api/ApiMissingArgumentException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
class ApiMissingArgumentException extends SimpleException
|
||||
{
|
||||
public function __construct($argumentName)
|
||||
{
|
||||
parent::__construct('Expected argument "' . $argumentName . '" was not specified');
|
||||
}
|
||||
}
|
41
src/Api/Common/EntityRetrievers/CommentRetriever.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
class CommentRetriever implements IEntityRetriever
|
||||
{
|
||||
private $job;
|
||||
|
||||
public function __construct(IJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
}
|
||||
|
||||
public function getJob()
|
||||
{
|
||||
return $this->job;
|
||||
}
|
||||
|
||||
public function tryRetrieve()
|
||||
{
|
||||
if ($this->job->hasArgument(JobArgs::ARG_COMMENT_ENTITY))
|
||||
return $this->job->getArgument(JobArgs::ARG_COMMENT_ENTITY);
|
||||
|
||||
if ($this->job->hasArgument(JobArgs::ARG_COMMENT_ID))
|
||||
return CommentModel::getById($this->job->getArgument(JobArgs::ARG_COMMENT_ID));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function retrieve()
|
||||
{
|
||||
$comment = $this->tryRetrieve();
|
||||
if ($comment)
|
||||
return $comment;
|
||||
throw new ApiJobUnsatisfiedException($this->job);
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Alternative(
|
||||
JobArgs::ARG_COMMENT_ID,
|
||||
JobArgs::ARG_COMMENT_ENTITY);
|
||||
}
|
||||
}
|
9
src/Api/Common/EntityRetrievers/IEntityRetriever.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
interface IEntityRetriever
|
||||
{
|
||||
public function __construct(IJob $job);
|
||||
public function getJob();
|
||||
public function tryRetrieve();
|
||||
public function retrieve();
|
||||
public function getRequiredArguments();
|
||||
}
|
68
src/Api/Common/EntityRetrievers/PostRetriever.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
class PostRetriever implements IEntityRetriever
|
||||
{
|
||||
private $job;
|
||||
|
||||
public function __construct(IJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
}
|
||||
|
||||
public function getJob()
|
||||
{
|
||||
return $this->job;
|
||||
}
|
||||
|
||||
public function tryRetrieve()
|
||||
{
|
||||
if ($this->job->hasArgument(JobArgs::ARG_POST_ENTITY))
|
||||
return $this->job->getArgument(JobArgs::ARG_POST_ENTITY);
|
||||
|
||||
if ($this->job->hasArgument(JobArgs::ARG_POST_ID))
|
||||
return PostModel::getById($this->job->getArgument(JobArgs::ARG_POST_ID));
|
||||
|
||||
if ($this->job->hasArgument(JobArgs::ARG_POST_NAME))
|
||||
return PostModel::getByName($this->job->getArgument(JobArgs::ARG_POST_NAME));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function retrieve()
|
||||
{
|
||||
$post = $this->tryRetrieve();
|
||||
if ($post)
|
||||
return $post;
|
||||
throw new ApiJobUnsatisfiedException($this->job);
|
||||
}
|
||||
|
||||
public function retrieveForEditing()
|
||||
{
|
||||
$post = $this->retrieve();
|
||||
if ($this->job->getContext() === IJob::CONTEXT_BATCH_ADD)
|
||||
return $post;
|
||||
|
||||
$expectedRevision = $this->job->getArgument(JobArgs::ARG_POST_REVISION);
|
||||
if ($expectedRevision != $post->getRevision())
|
||||
throw new SimpleException('This post was already edited by someone else in the meantime');
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Alternative(
|
||||
JobArgs::ARG_POST_ID,
|
||||
JobArgs::ARG_POST_NAME,
|
||||
JobArgs::ARG_POST_ENTITY);
|
||||
}
|
||||
|
||||
public function getRequiredArgumentsForEditing()
|
||||
{
|
||||
if ($this->job->getContext() === IJob::CONTEXT_BATCH_ADD)
|
||||
return $this->getRequiredArguments();
|
||||
|
||||
return JobArgs::Conjunction(
|
||||
$this->getRequiredArguments(),
|
||||
JobArgs::ARG_POST_REVISION);
|
||||
}
|
||||
}
|
41
src/Api/Common/EntityRetrievers/SafePostRetriever.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
class SafePostRetriever implements IEntityRetriever
|
||||
{
|
||||
private $job;
|
||||
|
||||
public function __construct(IJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
}
|
||||
|
||||
public function getJob()
|
||||
{
|
||||
return $this->job;
|
||||
}
|
||||
|
||||
public function tryRetrieve()
|
||||
{
|
||||
if ($this->job->hasArgument(JobArgs::ARG_POST_ENTITY))
|
||||
return $this->job->getArgument(JobArgs::ARG_POST_ENTITY);
|
||||
|
||||
if ($this->job->hasArgument(JobArgs::ARG_POST_NAME))
|
||||
return PostModel::getByName($this->job->getArgument(JobArgs::ARG_POST_NAME));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function retrieve()
|
||||
{
|
||||
$post = $this->tryRetrieve();
|
||||
if ($post)
|
||||
return $post;
|
||||
throw new ApiJobUnsatisfiedException($this->job);
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Alternative(
|
||||
JobArgs::ARG_POST_NAME,
|
||||
JobArgs::ARG_POST_ENTITY);
|
||||
}
|
||||
}
|
45
src/Api/Common/EntityRetrievers/UserRetriever.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
class UserRetriever implements IEntityRetriever
|
||||
{
|
||||
private $job;
|
||||
|
||||
public function __construct(IJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
}
|
||||
|
||||
public function getJob()
|
||||
{
|
||||
return $this->job;
|
||||
}
|
||||
|
||||
public function tryRetrieve()
|
||||
{
|
||||
if ($this->job->hasArgument(JobArgs::ARG_USER_ENTITY))
|
||||
return $this->job->getArgument(JobArgs::ARG_USER_ENTITY);
|
||||
|
||||
if ($this->job->hasArgument(JobArgs::ARG_USER_EMAIL))
|
||||
return UserModel::getByEmail($this->job->getArgument(JobArgs::ARG_USER_EMAIL));
|
||||
|
||||
if ($this->job->hasArgument(JobArgs::ARG_USER_NAME))
|
||||
return UserModel::getByName($this->job->getArgument(JobArgs::ARG_USER_NAME));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function retrieve()
|
||||
{
|
||||
$user = $this->tryRetrieve();
|
||||
if ($user)
|
||||
return $user;
|
||||
throw new ApiJobUnsatisfiedException($this->job);
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Alternative(
|
||||
JobArgs::ARG_USER_NAME,
|
||||
JobArgs::ARG_USER_EMAIL,
|
||||
JobArgs::ARG_USER_ENTITY);
|
||||
}
|
||||
}
|
50
src/Api/Common/JobPager.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
class JobPager
|
||||
{
|
||||
private $job;
|
||||
|
||||
public function __construct(IJob $job)
|
||||
{
|
||||
$this->pageSize = 20;
|
||||
$this->job = $job;
|
||||
}
|
||||
|
||||
public function setPageSize($newPageSize)
|
||||
{
|
||||
$this->pageSize = $newPageSize;
|
||||
}
|
||||
|
||||
public function getPageSize()
|
||||
{
|
||||
return $this->pageSize;
|
||||
}
|
||||
|
||||
public function getPageNumber()
|
||||
{
|
||||
if ($this->job->hasArgument(JobArgs::ARG_PAGE_NUMBER))
|
||||
return (int) $this->job->getArgument(JobArgs::ARG_PAGE_NUMBER);
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Optional(JobArgs::ARG_PAGE_NUMBER);
|
||||
}
|
||||
|
||||
public function serialize($entities, $totalEntityCount)
|
||||
{
|
||||
$pageSize = $this->getPageSize();
|
||||
$pageNumber = $this->getPageNumber();
|
||||
|
||||
$pageCount = (int) ceil($totalEntityCount / $pageSize);
|
||||
$pageNumber = $this->getPageNumber();
|
||||
$pageNumber = min($pageCount, $pageNumber);
|
||||
|
||||
$ret = new StdClass;
|
||||
$ret->entities = $entities;
|
||||
$ret->entityCount = (int) $totalEntityCount;
|
||||
$ret->page = (int) $pageNumber;
|
||||
$ret->pageCount = (int) $pageCount;
|
||||
return $ret;
|
||||
}
|
||||
}
|
75
src/Api/JobArgs/JobArgs.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
class JobArgs
|
||||
{
|
||||
const ARG_ANONYMOUS = 'anonymous';
|
||||
|
||||
const ARG_PAGE_NUMBER = 'page-number';
|
||||
const ARG_QUERY = 'query';
|
||||
const ARG_TOKEN = 'token';
|
||||
|
||||
const ARG_USER_ENTITY = 'user';
|
||||
#const ARG_USER_ID = 'user-id';
|
||||
const ARG_USER_NAME = 'user-name';
|
||||
const ARG_USER_EMAIL = 'user-email';
|
||||
|
||||
const ARG_POST_ENTITY = 'post';
|
||||
const ARG_POST_ID = 'post-id';
|
||||
const ARG_POST_NAME = 'post-name';
|
||||
const ARG_POST_REVISION = 'post-revision';
|
||||
|
||||
const ARG_TAG_NAME = 'tag-name';
|
||||
const ARG_TAG_NAMES = 'tag-names';
|
||||
|
||||
const ARG_COMMENT_ENTITY = 'comment';
|
||||
const ARG_COMMENT_ID = 'comment-id';
|
||||
|
||||
const ARG_LOG_ID = 'log-id';
|
||||
|
||||
const ARG_NEW_TEXT = 'new-text';
|
||||
const ARG_NEW_STATE = 'new-state';
|
||||
|
||||
const ARG_NEW_POST_CONTENT = 'new-post-content';
|
||||
const ARG_NEW_POST_CONTENT_URL = 'new-post-content-url';
|
||||
const ARG_NEW_RELATED_POST_IDS = 'new-related-post-ids';
|
||||
const ARG_NEW_SAFETY = 'new-safety';
|
||||
const ARG_NEW_SOURCE = 'new-source';
|
||||
const ARG_NEW_THUMBNAIL_CONTENT = 'new-thumbnail-content';
|
||||
const ARG_NEW_TAG_NAMES = 'new-tag-names';
|
||||
|
||||
const ARG_NEW_ACCESS_RANK = 'new-access-rank';
|
||||
const ARG_NEW_EMAIL = 'new-email';
|
||||
const ARG_NEW_USER_NAME = 'new-user-name';
|
||||
const ARG_NEW_PASSWORD = 'new-password';
|
||||
const ARG_NEW_AVATAR_CONTENT = 'new-avatar-content';
|
||||
const ARG_NEW_AVATAR_STYLE = 'new-avatar-style';
|
||||
const ARG_NEW_SETTINGS = 'new-settings';
|
||||
|
||||
const ARG_NEW_POST_SCORE = 'new-post-score';
|
||||
const ARG_SOURCE_TAG_NAME = 'source-tag-name';
|
||||
const ARG_TARGET_TAG_NAME = 'target-tag-name';
|
||||
|
||||
public static function Alternative()
|
||||
{
|
||||
return JobArgsAlternative::factory(func_get_args());
|
||||
}
|
||||
|
||||
public static function Conjunction()
|
||||
{
|
||||
return JobArgsConjunction::factory(func_get_args());
|
||||
}
|
||||
|
||||
public static function Optional()
|
||||
{
|
||||
return JobArgsOptional::factory(func_get_args());
|
||||
}
|
||||
|
||||
public static function getInternalArguments()
|
||||
{
|
||||
return
|
||||
[
|
||||
self::ARG_POST_ENTITY,
|
||||
self::ARG_USER_ENTITY,
|
||||
self::ARG_COMMENT_ENTITY
|
||||
];
|
||||
}
|
||||
}
|
25
src/Api/JobArgs/JobArgsAlternative.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
class JobArgsAlternative extends JobArgsNestedStruct
|
||||
{
|
||||
/**
|
||||
* Simplifies the structure as much as possible
|
||||
* and returns new class or existing args.
|
||||
*/
|
||||
public static function factory(array $args)
|
||||
{
|
||||
$finalArgs = [];
|
||||
|
||||
foreach ($args as $arg)
|
||||
{
|
||||
if ($arg instanceof self)
|
||||
$finalArgs = array_merge($finalArgs, $arg->args);
|
||||
elseif ($arg !== null)
|
||||
$finalArgs []= $arg;
|
||||
}
|
||||
|
||||
if (count($finalArgs) == 1)
|
||||
return $finalArgs[0];
|
||||
else
|
||||
return new self($finalArgs);
|
||||
}
|
||||
}
|
25
src/Api/JobArgs/JobArgsConjunction.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
class JobArgsConjunction extends JobArgsNestedStruct
|
||||
{
|
||||
/**
|
||||
* Simplifies the structure as much as possible
|
||||
* and returns new class or existing args.
|
||||
*/
|
||||
public static function factory(array $args)
|
||||
{
|
||||
$finalArgs = [];
|
||||
|
||||
foreach ($args as $arg)
|
||||
{
|
||||
if ($arg instanceof self)
|
||||
$finalArgs = array_merge($finalArgs, $arg->args);
|
||||
elseif ($arg !== null)
|
||||
$finalArgs []= $arg;
|
||||
}
|
||||
|
||||
if (count($finalArgs) == 1)
|
||||
return $finalArgs[0];
|
||||
else
|
||||
return new self($finalArgs);
|
||||
}
|
||||
}
|
19
src/Api/JobArgs/JobArgsNestedStruct.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
class JobArgsNestedStruct
|
||||
{
|
||||
public $args;
|
||||
|
||||
protected function __construct(array $args)
|
||||
{
|
||||
usort($args, function($arg1, $arg2)
|
||||
{
|
||||
return strnatcasecmp(serialize($arg1), serialize($arg2));
|
||||
});
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
public static function factory(array $args)
|
||||
{
|
||||
throw new BadMethodCallException('Not implemented');
|
||||
}
|
||||
}
|
20
src/Api/JobArgs/JobArgsOptional.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
class JobArgsOptional extends JobArgsNestedStruct
|
||||
{
|
||||
/**
|
||||
* Simplifies the structure as much as possible
|
||||
* and returns new class or existing args.
|
||||
*/
|
||||
public static function factory(array $args)
|
||||
{
|
||||
$args = array_filter($args, function($arg)
|
||||
{
|
||||
return $arg !== null;
|
||||
});
|
||||
|
||||
if (count($args) == 0)
|
||||
return null;
|
||||
else
|
||||
return new self($args);
|
||||
}
|
||||
}
|
79
src/Api/Jobs/AbstractJob.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
abstract class AbstractJob implements IJob
|
||||
{
|
||||
protected $arguments = [];
|
||||
protected $context = self::CONTEXT_NORMAL;
|
||||
protected $subJobs;
|
||||
|
||||
public function prepare()
|
||||
{
|
||||
}
|
||||
|
||||
public abstract function execute();
|
||||
|
||||
public abstract function getRequiredArguments();
|
||||
|
||||
public function isAvailableToPublic()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$name = get_called_class();
|
||||
$name = str_replace('Job', '', $name);
|
||||
$name = TextCaseConverter::convert(
|
||||
$name,
|
||||
TextCaseConverter::UPPER_CAMEL_CASE,
|
||||
TextCaseConverter::SPINAL_CASE);
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function addSubJob(IJob $subJob)
|
||||
{
|
||||
$this->subJobs []= $subJob;
|
||||
}
|
||||
|
||||
public function getSubJobs()
|
||||
{
|
||||
return $this->subJobs;
|
||||
}
|
||||
|
||||
public function getContext()
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function setContext($context)
|
||||
{
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function getArgument($key)
|
||||
{
|
||||
if (!$this->hasArgument($key))
|
||||
throw new ApiMissingArgumentException($key);
|
||||
|
||||
return $this->arguments[$key];
|
||||
}
|
||||
|
||||
public function getArguments()
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function hasArgument($key)
|
||||
{
|
||||
return isset($this->arguments[$key]);
|
||||
}
|
||||
|
||||
public function setArgument($key, $value)
|
||||
{
|
||||
$this->arguments[$key] = $value;
|
||||
}
|
||||
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
}
|
57
src/Api/Jobs/CommentJobs/AddCommentJob.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
class AddCommentJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
$user = Auth::getCurrentUser();
|
||||
$text = $this->getArgument(JobArgs::ARG_NEW_TEXT);
|
||||
|
||||
$comment = CommentModel::spawn();
|
||||
$comment->setCommenter($user);
|
||||
$comment->setPost($post);
|
||||
$comment->setCreationTime(time());
|
||||
$comment->setText($text);
|
||||
|
||||
CommentModel::save($comment);
|
||||
Logger::log('{user} commented on {post}', [
|
||||
'user' => TextHelper::reprUser($user),
|
||||
'post' => TextHelper::reprPost($comment->getPost())]);
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArguments(),
|
||||
JobArgs::ARG_NEW_TEXT);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::AddComment;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return Core::getConfig()->comments->needEmailForCommenting;
|
||||
}
|
||||
}
|
47
src/Api/Jobs/CommentJobs/DeleteCommentJob.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
class DeleteCommentJob extends AbstractJob
|
||||
{
|
||||
protected $commentRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->commentRetriever = new CommentRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$comment = $this->commentRetriever->retrieve();
|
||||
$post = $comment->getPost();
|
||||
|
||||
CommentModel::remove($comment);
|
||||
|
||||
Logger::log('{user} removed comment from {post}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post)]);
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return $this->commentRetriever->getRequiredArguments();
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::DeleteComment;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->commentRetriever->retrieve()->getCommenter());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
52
src/Api/Jobs/CommentJobs/EditCommentJob.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
class EditCommentJob extends AbstractJob
|
||||
{
|
||||
protected $commentRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->commentRetriever = new CommentRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$comment = $this->commentRetriever->retrieve();
|
||||
|
||||
$comment->setCreationTime(time());
|
||||
$comment->setText($this->getArgument(JobArgs::ARG_NEW_TEXT));
|
||||
|
||||
CommentModel::save($comment);
|
||||
Logger::log('{user} edited comment in {post}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($comment->getPost())]);
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->commentRetriever->getRequiredArguments(),
|
||||
JobArgs::ARG_NEW_TEXT);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::EditComment;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->commentRetriever->retrieve()->getCommenter());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
60
src/Api/Jobs/CommentJobs/ListCommentsJob.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
class ListCommentsJob extends AbstractJob implements IPagedJob
|
||||
{
|
||||
protected $pager;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pager = new JobPager($this);
|
||||
$this->pager->setPageSize(Core::getConfig()->comments->commentsPerPage);
|
||||
}
|
||||
|
||||
public function getPager()
|
||||
{
|
||||
return $this->pager;
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$pageSize = $this->pager->getPageSize();
|
||||
$page = $this->pager->getPageNumber();
|
||||
$query = 'comment_min:1 order:comment_date,desc';
|
||||
|
||||
$posts = PostSearchService::getEntities($query, $pageSize, $page);
|
||||
$postCount = PostSearchService::getEntityCount($query);
|
||||
|
||||
PostModel::preloadTags($posts);
|
||||
PostModel::preloadComments($posts);
|
||||
$comments = [];
|
||||
foreach ($posts as $post)
|
||||
$comments = array_merge($comments, $post->getComments());
|
||||
CommentModel::preloadCommenters($comments);
|
||||
|
||||
return $this->pager->serialize($posts, $postCount);
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return $this->pager->getRequiredArguments();
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::ListComments;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
63
src/Api/Jobs/CommentJobs/PreviewCommentJob.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
class PreviewCommentJob extends AbstractJob
|
||||
{
|
||||
protected $commentRetriever;
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->commentRetriever = new CommentRetriever($this);
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$user = Auth::getCurrentUser();
|
||||
$text = $this->getArgument(JobArgs::ARG_NEW_TEXT);
|
||||
|
||||
$comment = $this->commentRetriever->tryRetrieve();
|
||||
if (!$comment)
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
$comment = CommentModel::spawn();
|
||||
$comment->setPost($post);
|
||||
}
|
||||
|
||||
$comment->setCommenter($user);
|
||||
$comment->setCreationTime(time());
|
||||
$comment->setText($text);
|
||||
|
||||
$comment->validate();
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
JobArgs::ARG_NEW_TEXT,
|
||||
JobArgs::Alternative(
|
||||
$this->commentRetriever->getRequiredArguments(),
|
||||
$this->postRetriever->getRequiredArguments()));
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::AddComment;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return Core::getConfig()->comments->needEmailForCommenting;
|
||||
}
|
||||
}
|
34
src/Api/Jobs/GetPropertyJob.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
class GetPropertyJob extends AbstractJob
|
||||
{
|
||||
public function execute()
|
||||
{
|
||||
return PropertyModel::get($this->getArgument(JobArgs::ARG_QUERY));
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::ARG_QUERY;
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
26
src/Api/Jobs/IJob.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
interface IJob
|
||||
{
|
||||
const CONTEXT_NORMAL = 1;
|
||||
const CONTEXT_BATCH_EDIT = 2;
|
||||
const CONTEXT_BATCH_ADD = 3;
|
||||
|
||||
public function prepare();
|
||||
public function execute();
|
||||
|
||||
public function getContext();
|
||||
public function setContext($context);
|
||||
|
||||
public function getRequiredArguments();
|
||||
public function getRequiredMainPrivilege();
|
||||
public function getRequiredSubPrivileges();
|
||||
public function isAuthenticationRequired();
|
||||
public function isConfirmedEmailRequired();
|
||||
public function isAvailableToPublic();
|
||||
|
||||
public function getArgument($key);
|
||||
public function getArguments();
|
||||
public function hasArgument($key);
|
||||
public function setArgument($key, $value);
|
||||
public function setArguments(array $arguments);
|
||||
}
|
5
src/Api/Jobs/IPagedJob.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
interface IPagedJob
|
||||
{
|
||||
public function getPager();
|
||||
}
|
89
src/Api/Jobs/LogJobs/GetLogJob.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
class GetLogJob extends AbstractJob implements IPagedJob
|
||||
{
|
||||
protected $pager;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pager = new JobPager($this);
|
||||
$this->pager->setPageSize(Core::getConfig()->browsing->logsPerPage);
|
||||
}
|
||||
|
||||
public function getPager()
|
||||
{
|
||||
return $this->pager;
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$pageSize = $this->pager->getPageSize();
|
||||
$page = $this->pager->getPageNumber();
|
||||
$name = $this->getArgument(JobArgs::ARG_LOG_ID);
|
||||
$query = $this->hasArgument(JobArgs::ARG_QUERY)
|
||||
? $this->getArgument(JobArgs::ARG_QUERY)
|
||||
: '';
|
||||
|
||||
$page = max(1, intval($page));
|
||||
$path = $this->getPath($name);
|
||||
$lines = $this->loadLines($path);
|
||||
|
||||
if (!empty($query))
|
||||
{
|
||||
$lines = array_filter($lines, function($line) use ($query)
|
||||
{
|
||||
return stripos($line, $query) !== false;
|
||||
});
|
||||
}
|
||||
|
||||
$lineCount = count($lines);
|
||||
$lines = array_slice($lines, ($page - 1) * $pageSize, $pageSize);
|
||||
|
||||
return $this->pager->serialize($lines, $lineCount);
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->pager->getRequiredArguments(),
|
||||
JobArgs::ARG_LOG_ID,
|
||||
JobArgs::Optional(JobArgs::ARG_QUERY));
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::ViewLog;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getPath($name)
|
||||
{
|
||||
$name = str_replace(['/', '\\'], '', $name);
|
||||
return TextHelper::absolutePath(dirname(Core::getConfig()->main->logsPath) . DS . $name);
|
||||
}
|
||||
|
||||
private function loadLines($path)
|
||||
{
|
||||
if (!file_exists($path))
|
||||
throw new SimpleNotFoundException('Specified log doesn\'t exist');
|
||||
|
||||
$lines = file_get_contents($path);
|
||||
$lines = trim($lines);
|
||||
$lines = explode(PHP_EOL, str_replace(["\r", "\n"], PHP_EOL, $lines));
|
||||
$lines = array_reverse($lines);
|
||||
return $lines;
|
||||
}
|
||||
}
|
41
src/Api/Jobs/LogJobs/ListLogsJob.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
class ListLogsJob extends AbstractJob
|
||||
{
|
||||
public function execute()
|
||||
{
|
||||
$path = TextHelper::absolutePath(Core::getConfig()->main->logsPath);
|
||||
|
||||
$logs = [];
|
||||
foreach (glob(dirname($path) . DS . '*.log') as $log)
|
||||
$logs []= basename($log);
|
||||
|
||||
natcasesort($logs);
|
||||
$logs = array_reverse($logs);
|
||||
return $logs;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::ListLogs;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
93
src/Api/Jobs/PostJobs/AddPostJob.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
class AddPostJob extends AbstractJob
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->addSubJob(new EditPostSafetyJob());
|
||||
$this->addSubJob(new EditPostTagsJob());
|
||||
$this->addSubJob(new EditPostSourceJob());
|
||||
$this->addSubJob(new EditPostRelationsJob());
|
||||
$this->addSubJob(new EditPostContentJob());
|
||||
$this->addSubJob(new EditPostThumbnailJob());
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = PostModel::spawn();
|
||||
|
||||
$anonymous = false;
|
||||
if ($this->hasArgument(JobArgs::ARG_ANONYMOUS))
|
||||
$anonymous = TextHelper::toBoolean($this->getArgument(JobArgs::ARG_ANONYMOUS));
|
||||
if ($anonymous and !Core::getConfig()->uploads->allowAnonymousUploads)
|
||||
throw new SimpleException('Anonymous uploads are not allowed');
|
||||
|
||||
if (Auth::isLoggedIn() and !$anonymous)
|
||||
$post->setUploader(Auth::getCurrentUser());
|
||||
|
||||
PostModel::forgeId($post);
|
||||
|
||||
$arguments = $this->getArguments();
|
||||
$arguments[JobArgs::ARG_POST_ENTITY] = $post;
|
||||
|
||||
$this->runSubJobs($this->getSubJobs(), $arguments);
|
||||
PostModel::save($post);
|
||||
|
||||
Logger::log('{user} added {post} (tags: {tags}, safety: {safety}, source: {source})', [
|
||||
'user' => ($anonymous and !Core::getConfig()->uploads->logAnonymousUploadsNicknames)
|
||||
? TextHelper::reprUser(UserModel::getAnonymousName())
|
||||
: TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'tags' => TextHelper::reprTags($post->getTags()),
|
||||
'safety' => $post->getSafety()->toString(),
|
||||
'source' => $post->getSource()]);
|
||||
|
||||
Logger::flush();
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Optional(JobArgs::ARG_ANONYMOUS);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::AddPost;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return Core::getConfig()->uploads->needEmailForUploading;
|
||||
}
|
||||
|
||||
private function runSubJobs($subJobs, $arguments)
|
||||
{
|
||||
foreach ($subJobs as $subJob)
|
||||
{
|
||||
Logger::bufferChanges();
|
||||
$subJob->setContext(self::CONTEXT_BATCH_ADD);
|
||||
try
|
||||
{
|
||||
Api::run($subJob, $arguments);
|
||||
}
|
||||
catch (ApiJobUnsatisfiedException $e)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
Logger::discardBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
src/Api/Jobs/PostJobs/DeletePostJob.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
class DeletePostJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
|
||||
PostModel::remove($post);
|
||||
|
||||
Logger::log('{user} deleted {post}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post)]);
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return $this->postRetriever->getRequiredArguments();
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::DeletePost;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
65
src/Api/Jobs/PostJobs/EditPostContentJob.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
class EditPostContentJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieveForEditing();
|
||||
if ($this->hasArgument(JobArgs::ARG_NEW_POST_CONTENT_URL))
|
||||
{
|
||||
$url = $this->getArgument(JobArgs::ARG_NEW_POST_CONTENT_URL);
|
||||
$post->setContentFromUrl($url);
|
||||
}
|
||||
else
|
||||
{
|
||||
$file = $this->getArgument(JobArgs::ARG_NEW_POST_CONTENT);
|
||||
$post->setContentFromPath($file->filePath, $file->fileName);
|
||||
}
|
||||
|
||||
if ($this->getContext() == self::CONTEXT_NORMAL)
|
||||
PostModel::save($post);
|
||||
|
||||
Logger::log('{user} changed contents of {post}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post)]);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArgumentsForEditing(),
|
||||
JobArgs::Alternative(
|
||||
JobArgs::ARG_NEW_POST_CONTENT,
|
||||
JobArgs::ARG_NEW_POST_CONTENT_URL));
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return $this->getContext() == self::CONTEXT_BATCH_ADD
|
||||
? Privilege::AddPostContent
|
||||
: Privilege::EditPostContent;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
67
src/Api/Jobs/PostJobs/EditPostJob.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
class EditPostJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
$this->addSubJob(new EditPostSafetyJob());
|
||||
$this->addSubJob(new EditPostTagsJob());
|
||||
$this->addSubJob(new EditPostSourceJob());
|
||||
$this->addSubJob(new EditPostRelationsJob());
|
||||
$this->addSubJob(new EditPostContentJob());
|
||||
$this->addSubJob(new EditPostThumbnailJob());
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieveForEditing();
|
||||
$arguments = $this->getArguments();
|
||||
$arguments[JobArgs::ARG_POST_ENTITY] = $post;
|
||||
|
||||
Logger::bufferChanges();
|
||||
foreach ($this->getSubJobs() as $subJob)
|
||||
{
|
||||
$subJob->setContext(self::CONTEXT_BATCH_EDIT);
|
||||
|
||||
try
|
||||
{
|
||||
Api::run($subJob, $arguments);
|
||||
}
|
||||
catch (ApiJobUnsatisfiedException $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
PostModel::save($post);
|
||||
Logger::flush();
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return $this->postRetriever->getRequiredArgumentsForEditing();
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::EditPost;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
75
src/Api/Jobs/PostJobs/EditPostRelationsJob.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
class EditPostRelationsJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieveForEditing();
|
||||
$relatedPostIds = $this->getArgument(JobArgs::ARG_NEW_RELATED_POST_IDS);
|
||||
|
||||
if (!is_array($relatedPostIds))
|
||||
throw new SimpleException('Expected array');
|
||||
|
||||
$relatedPosts = PostModel::getAllByIds($relatedPostIds);
|
||||
|
||||
$oldRelatedIds = array_map(function($post) { return $post->getId(); }, $post->getRelations());
|
||||
$post->setRelations($relatedPosts);
|
||||
$newRelatedIds = array_map(function($post) { return $post->getId(); }, $post->getRelations());
|
||||
|
||||
if ($this->getContext() == self::CONTEXT_NORMAL)
|
||||
PostModel::save($post);
|
||||
|
||||
foreach (array_diff($oldRelatedIds, $newRelatedIds) as $post2id)
|
||||
{
|
||||
Logger::log('{user} removed relation between {post} and {post2}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'post2' => TextHelper::reprPost($post2id)]);
|
||||
}
|
||||
|
||||
foreach (array_diff($newRelatedIds, $oldRelatedIds) as $post2id)
|
||||
{
|
||||
Logger::log('{user} added relation between {post} and {post2}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'post2' => TextHelper::reprPost($post2id)]);
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArgumentsForEditing(),
|
||||
JobArgs::ARG_NEW_RELATED_POST_IDS);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return $this->getContext() == self::CONTEXT_BATCH_ADD
|
||||
? Privilege::AddPostRelations
|
||||
: Privilege::EditPostRelations;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
61
src/Api/Jobs/PostJobs/EditPostSafetyJob.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
class EditPostSafetyJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieveForEditing();
|
||||
$newSafety = new PostSafety($this->getArgument(JobArgs::ARG_NEW_SAFETY));
|
||||
|
||||
$oldSafety = $post->getSafety();
|
||||
$post->setSafety($newSafety);
|
||||
|
||||
if ($this->getContext() == self::CONTEXT_NORMAL)
|
||||
PostModel::save($post);
|
||||
|
||||
if ($oldSafety != $newSafety)
|
||||
{
|
||||
Logger::log('{user} changed safety of {post} to {safety}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'safety' => $post->getSafety()->toString()]);
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArgumentsForEditing(),
|
||||
JobArgs::ARG_NEW_SAFETY);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return $this->getContext() == self::CONTEXT_BATCH_ADD
|
||||
? Privilege::AddPostSafety
|
||||
: Privilege::EditPostSafety;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
61
src/Api/Jobs/PostJobs/EditPostSourceJob.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
class EditPostSourceJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieveForEditing();
|
||||
$newSource = $this->getArgument(JobArgs::ARG_NEW_SOURCE);
|
||||
|
||||
$oldSource = $post->getSource();
|
||||
$post->setSource($newSource);
|
||||
|
||||
if ($this->getContext() == self::CONTEXT_NORMAL)
|
||||
PostModel::save($post);
|
||||
|
||||
if ($oldSource != $newSource)
|
||||
{
|
||||
Logger::log('{user} changed source of {post} to {source}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'source' => $post->getSource()]);
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArgumentsForEditing(),
|
||||
JobArgs::ARG_NEW_SOURCE);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return $this->getContext() == self::CONTEXT_BATCH_ADD
|
||||
? Privilege::AddPostSource
|
||||
: Privilege::EditPostSource;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
78
src/Api/Jobs/PostJobs/EditPostTagsJob.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
class EditPostTagsJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieveForEditing();
|
||||
$tagNames = $this->getArgument(JobArgs::ARG_NEW_TAG_NAMES);
|
||||
|
||||
if (!is_array($tagNames))
|
||||
throw new SimpleException('Expected array');
|
||||
|
||||
$tags = TagModel::spawnFromNames($tagNames);
|
||||
|
||||
$oldTags = array_map(function($tag) { return $tag->getName(); }, $post->getTags());
|
||||
$post->setTags($tags);
|
||||
$newTags = array_map(function($tag) { return $tag->getName(); }, $post->getTags());
|
||||
|
||||
if ($this->getContext() == self::CONTEXT_NORMAL)
|
||||
{
|
||||
PostModel::save($post);
|
||||
TagModel::removeUnused();
|
||||
}
|
||||
|
||||
foreach (array_diff($oldTags, $newTags) as $tag)
|
||||
{
|
||||
Logger::log('{user} untagged {post} with {tag}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'tag' => TextHelper::reprTag($tag)]);
|
||||
}
|
||||
|
||||
foreach (array_diff($newTags, $oldTags) as $tag)
|
||||
{
|
||||
Logger::log('{user} tagged {post} with {tag}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'tag' => TextHelper::reprTag($tag)]);
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArgumentsForEditing(),
|
||||
JobArgs::ARG_NEW_TAG_NAMES);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return $this->getContext() == self::CONTEXT_BATCH_ADD
|
||||
? Privilege::AddPostTags
|
||||
: Privilege::EditPostTags;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
56
src/Api/Jobs/PostJobs/EditPostThumbnailJob.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
class EditPostThumbnailJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieveForEditing();
|
||||
$file = $this->getArgument(JobArgs::ARG_NEW_THUMBNAIL_CONTENT);
|
||||
|
||||
$post->setCustomThumbnailFromPath($file->filePath);
|
||||
|
||||
if ($this->getContext() == self::CONTEXT_NORMAL)
|
||||
PostModel::save($post);
|
||||
|
||||
Logger::log('{user} changed thumb of {post}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post)]);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArgumentsForEditing(),
|
||||
JobArgs::ARG_NEW_THUMBNAIL_CONTENT);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return $this->getContext() == self::CONTEXT_BATCH_ADD
|
||||
? Privilege::AddPostThumbnail
|
||||
: Privilege::EditPostThumbnail;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
60
src/Api/Jobs/PostJobs/FeaturePostJob.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
class FeaturePostJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
|
||||
PropertyModel::set(PropertyModel::FeaturedPostId, $post->getId());
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUnixTime, time());
|
||||
|
||||
$anonymous = false;
|
||||
if ($this->hasArgument(JobArgs::ARG_ANONYMOUS))
|
||||
$anonymous = TextHelper::toBoolean($this->getArgument(JobArgs::ARG_ANONYMOUS));
|
||||
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUserName,
|
||||
$anonymous
|
||||
? null
|
||||
: Auth::getCurrentUser()->getName());
|
||||
|
||||
Logger::log('{user} featured {post} on main page', [
|
||||
'user' => TextHelper::reprUser(PropertyModel::get(PropertyModel::FeaturedPostUserName)),
|
||||
'post' => TextHelper::reprPost($post)]);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArguments(),
|
||||
JobArgs::Optional(JobArgs::ARG_ANONYMOUS));
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::FeaturePost;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
53
src/Api/Jobs/PostJobs/FlagPostJob.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
class FlagPostJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
$key = TextHelper::reprPost($post);
|
||||
|
||||
$flagged = SessionHelper::get('flagged', []);
|
||||
if (in_array($key, $flagged))
|
||||
throw new SimpleException('You already flagged this post');
|
||||
$flagged []= $key;
|
||||
SessionHelper::set('flagged', $flagged);
|
||||
|
||||
Logger::log('{user} flagged {post} for moderator attention', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post)]);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return $this->postRetriever->getRequiredArguments();
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::FlagPost;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
56
src/Api/Jobs/PostJobs/GetPostContentJob.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
class GetPostContentJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new SafePostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
$config = Core::getConfig();
|
||||
|
||||
$path = $post->getContentPath();
|
||||
if (!file_exists($path))
|
||||
throw new SimpleNotFoundException('Post file does not exist');
|
||||
if (!is_readable($path))
|
||||
throw new SimpleException('Post file is not readable');
|
||||
|
||||
$fileName = sprintf('%s_%s_%s.%s',
|
||||
$config->appearance->title,
|
||||
$post->getId(),
|
||||
join(',', array_map(function($tag) { return $tag->getName(); }, $post->getTags())),
|
||||
TextHelper::resolveMimeType($post->getMimeType()) ?: 'dat');
|
||||
$fileName = preg_replace('/[[:^print:]]/', '', $fileName);
|
||||
|
||||
return new ApiFileOutput($path, $fileName);
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return $this->postRetriever->getRequiredArguments();
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
54
src/Api/Jobs/PostJobs/GetPostJob.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
class GetPostJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
|
||||
CommentModel::preloadCommenters($post->getComments());
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArguments(),
|
||||
null);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::ViewPost;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
$privileges = [];
|
||||
|
||||
if ($post->isHidden())
|
||||
$privileges []= 'hidden';
|
||||
|
||||
$privileges []= $post->getSafety()->toString();
|
||||
|
||||
return $privileges;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
71
src/Api/Jobs/PostJobs/GetPostThumbnailJob.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
class GetPostThumbnailJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new SafePostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
|
||||
$path = $post->getThumbnailPath();
|
||||
if (!$this->isValidThumbnailPath($path))
|
||||
{
|
||||
try
|
||||
{
|
||||
$post->generateThumbnail();
|
||||
$path = $post->getThumbnailPath();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$path = null;
|
||||
}
|
||||
|
||||
if (!$this->isValidThumbnailPath($path))
|
||||
$path = $this->getDefaultThumbnailPath();
|
||||
}
|
||||
|
||||
return new ApiFileOutput($path, 'thumbnail.jpg');
|
||||
}
|
||||
|
||||
private function isValidThumbnailPath($path)
|
||||
{
|
||||
return file_exists($path) and is_readable($path);
|
||||
}
|
||||
|
||||
private function getDefaultThumbnailPath()
|
||||
{
|
||||
$path = Core::getConfig()->main->mediaPath . DS . 'img' . DS . 'thumb.jpg';
|
||||
$path = TextHelper::absolutePath($path);
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return $this->postRetriever->getRequiredArguments();
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
59
src/Api/Jobs/PostJobs/ListPostsJob.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
class ListPostsJob extends AbstractJob implements IPagedJob
|
||||
{
|
||||
protected $pager;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pager = new JobPager($this);
|
||||
$this->pager->setPageSize(Core::getConfig()->browsing->postsPerPage);
|
||||
}
|
||||
|
||||
public function getPager()
|
||||
{
|
||||
return $this->pager;
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$pageSize = $this->pager->getPageSize();
|
||||
$page = $this->pager->getPageNumber();
|
||||
$query = $this->hasArgument(JobArgs::ARG_QUERY)
|
||||
? $this->getArgument(JobArgs::ARG_QUERY)
|
||||
: '';
|
||||
|
||||
$posts = PostSearchService::getEntities($query, $pageSize, $page);
|
||||
$postCount = PostSearchService::getEntityCount($query);
|
||||
|
||||
PostModel::preloadTags($posts);
|
||||
|
||||
return $this->pager->serialize($posts, $postCount);
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->pager->getRequiredArguments(),
|
||||
JobArgs::Optional(JobArgs::ARG_QUERY));
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::ListPosts;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
47
src/Api/Jobs/PostJobs/ScorePostJob.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
class ScorePostJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
$score = TextHelper::toInteger($this->getArgument(JobArgs::ARG_NEW_POST_SCORE));
|
||||
|
||||
UserModel::updateUserScore(Auth::getCurrentUser(), $post, $score);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArguments(),
|
||||
JobArgs::ARG_NEW_POST_SCORE);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::ScorePost;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
55
src/Api/Jobs/PostJobs/TogglePostFavoriteJob.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
class TogglePostFavoriteJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->postRetriever->retrieve();
|
||||
$favorite = TextHelper::toBoolean($this->getArgument(JobArgs::ARG_NEW_STATE));
|
||||
|
||||
if ($favorite)
|
||||
{
|
||||
UserModel::updateUserScore(Auth::getCurrentUser(), $post, 1);
|
||||
UserModel::addToUserFavorites(Auth::getCurrentUser(), $post);
|
||||
}
|
||||
else
|
||||
{
|
||||
UserModel::removeFromUserFavorites(Auth::getCurrentUser(), $post);
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArguments(),
|
||||
JobArgs::ARG_NEW_STATE);
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::FavoritePost;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
88
src/Api/Jobs/PostJobs/TogglePostTagJob.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
class TogglePostTagJob extends AbstractJob
|
||||
{
|
||||
protected $postRetriever;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->postRetriever = new PostRetriever($this);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$tagName = $this->getArgument(JobArgs::ARG_TAG_NAME);
|
||||
$enable = TextHelper::toBoolean($this->getArgument(JobArgs::ARG_NEW_STATE));
|
||||
$post = $this->postRetriever->retrieve();
|
||||
|
||||
$tags = $post->getTags();
|
||||
|
||||
if ($enable)
|
||||
{
|
||||
$tag = TagModel::tryGetByName($tagName);
|
||||
if ($tag === null)
|
||||
{
|
||||
$tag = TagModel::spawn();
|
||||
$tag->setName($tagName);
|
||||
TagModel::save($tag);
|
||||
}
|
||||
|
||||
$tags []= $tag;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ($tags as $i => $tag)
|
||||
if ($tag->getName() == $tagName)
|
||||
unset($tags[$i]);
|
||||
}
|
||||
|
||||
$post->setTags($tags);
|
||||
PostModel::save($post);
|
||||
TagModel::removeUnused();
|
||||
|
||||
if ($enable)
|
||||
{
|
||||
Logger::log('{user} tagged {post} with {tag}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'tag' => TextHelper::reprTag($tagName)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::log('{user} untagged {post} with {tag}', [
|
||||
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'tag' => TextHelper::reprTag($tagName)]);
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getRequiredArguments()
|
||||
{
|
||||
return JobArgs::Conjunction(
|
||||
$this->postRetriever->getRequiredArguments(),
|
||||
JobArgs::Conjunction(
|
||||
JobArgs::ARG_TAG_NAME,
|
||||
Jobargs::ARG_NEW_STATE));
|
||||
}
|
||||
|
||||
public function getRequiredMainPrivilege()
|
||||
{
|
||||
return Privilege::EditPostTags;
|
||||
}
|
||||
|
||||
public function getRequiredSubPrivileges()
|
||||
{
|
||||
return Access::getIdentity($this->postRetriever->retrieve()->getUploader());
|
||||
}
|
||||
|
||||
public function isAuthenticationRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isConfirmedEmailRequired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|