mirror of
https://github.com/openvk/openvk
synced 2024-12-22 16:42:32 +03:00
Add early implementation of wiki pages
This commit is contained in:
parent
bb055f90aa
commit
d1e55fd53d
17 changed files with 599 additions and 37 deletions
|
@ -2,8 +2,8 @@
|
|||
namespace openvk\Web\Models\Entities;
|
||||
use openvk\Web\Util\DateTime;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Entities\{User, Manager};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Managers};
|
||||
use openvk\Web\Models\Entities\{User, Manager, WikiPage};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Managers, WikiPages};
|
||||
use Nette\Database\Table\{ActiveRow, GroupedSelection};
|
||||
use Chandler\Database\DatabaseConnection as DB;
|
||||
use Chandler\Security\User as ChandlerUser;
|
||||
|
@ -328,6 +328,31 @@ class Club extends RowModel
|
|||
])->delete();
|
||||
}
|
||||
|
||||
function containsWiki(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->pages;
|
||||
}
|
||||
|
||||
function getWikiHomePage(): ?WikiPage
|
||||
{
|
||||
return (new WikiPages)->getByOwnerAndVID($this->getId() * -1, 1);
|
||||
}
|
||||
|
||||
function setWikiEnabled(bool $enable = true): void
|
||||
{
|
||||
if($enable) {
|
||||
if(is_null((new WikiPages)->getByOwnerAndVID($this->getId() * -1, 1))) {
|
||||
$page = new WikiPage;
|
||||
$page->setOwner($this->getId() * -1);
|
||||
$page->setTitle("Fresh News");
|
||||
$page->setSource("");
|
||||
$page->save();
|
||||
}
|
||||
}
|
||||
|
||||
$this->stateChanges("pages", (int) $enable);
|
||||
}
|
||||
|
||||
function canBeModifiedBy(User $user): bool
|
||||
{
|
||||
$id = $user->getId();
|
||||
|
|
6
Web/Models/Entities/ILinkable.php
Normal file
6
Web/Models/Entities/ILinkable.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
|
||||
interface ILinkable {
|
||||
public function getOVKLink(): string;
|
||||
}
|
3
Web/Models/Entities/WikiPage.php
Normal file
3
Web/Models/Entities/WikiPage.php
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
class_alias('openvk\Web\Models\Entities\WikiPage\WikiPage', 'openvk\Web\Models\Entities\WikiPage');
|
213
Web/Models/Entities/WikiPage/Parser.php
Normal file
213
Web/Models/Entities/WikiPage/Parser.php
Normal file
|
@ -0,0 +1,213 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities\WikiPage;
|
||||
use openvk\Web\Models\Repositories\WikiPages;
|
||||
use Netcarver\Textile;
|
||||
use HTMLPurifier_Config;
|
||||
use HTMLPurifier;
|
||||
|
||||
class Parser
|
||||
{
|
||||
private $depth;
|
||||
private $page;
|
||||
private $repo;
|
||||
private $vars;
|
||||
private $ctx;
|
||||
|
||||
private $entityNames = [
|
||||
"id" => [0, "User", "get", "getAvatarURL"],
|
||||
"photo" => [1, "Photo", "getByOwnerAndVID", "getURL"],
|
||||
"video" => [1, "Video", "getByOwnerAndVID", "getThumbnailURL"],
|
||||
"note" => [1, "Note", "getNoteById", NULL],
|
||||
"club" => [0, "Group", "get", "getAvatarURL"],
|
||||
"wall" => [1, "Post", "getPostById", NULL],
|
||||
];
|
||||
|
||||
const REFERENCE_SINGULAR = 0;
|
||||
const REFERENCE_DUAL = 1;
|
||||
|
||||
function __construct(WikiPage $page, WikiPages $repo, int &$counter, array $vars = [], array $ctx = []) {
|
||||
$this->depth = $counter;
|
||||
$this->page = $page;
|
||||
$this->repo = $repo;
|
||||
$this->vars = $vars;
|
||||
$this->ctx = $ctx;
|
||||
}
|
||||
|
||||
private function getPurifier(): HTMLPurifier
|
||||
{
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set("Attr.AllowedClasses", ["unbordered", "inline", "nonexistent"]);
|
||||
$config->set("Attr.DefaultInvalidImageAlt", "Unknown image");
|
||||
$config->set("AutoFormat.AutoParagraph", true);
|
||||
$config->set("AutoFormat.Linkify", true);
|
||||
$config->set("URI.Base", "//$_SERVER[SERVER_NAME]/");
|
||||
$config->set("URI.Munge", "/away.php?xinf=%n.%m:%r&css=%p&to=%s");
|
||||
$config->set("URI.MakeAbsolute", true);
|
||||
$config->set("HTML.Doctype", "XHTML 1.1");
|
||||
$config->set("HTML.TidyLevel", "heavy");
|
||||
$config->set("HTML.AllowedElements", [
|
||||
"div",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"p",
|
||||
"i",
|
||||
"b",
|
||||
"a",
|
||||
"del",
|
||||
"ins",
|
||||
"sup",
|
||||
"sub",
|
||||
"table",
|
||||
"thead",
|
||||
"tbody",
|
||||
"tr",
|
||||
"td",
|
||||
"th",
|
||||
"img",
|
||||
"ul",
|
||||
"ol",
|
||||
"li",
|
||||
"hr",
|
||||
"br",
|
||||
"acronym",
|
||||
"blockquote",
|
||||
"cite",
|
||||
]);
|
||||
$config->set("HTML.AllowedAttributes", [
|
||||
"table.summary",
|
||||
"td.abbr",
|
||||
"th.abbr",
|
||||
"a.href",
|
||||
"img.src",
|
||||
"img.alt",
|
||||
"img.style",
|
||||
"div.style",
|
||||
"div.title",
|
||||
]);
|
||||
$config->set("CSS.AllowedProperties", [
|
||||
"float",
|
||||
"height",
|
||||
"width",
|
||||
"max-height",
|
||||
"max-width",
|
||||
"font-weight",
|
||||
]);
|
||||
|
||||
return new HTMLPurifier($config);;
|
||||
}
|
||||
|
||||
private function resolveEntityURL(array $matches, bool $dual = false, bool $needImage = false): ?string
|
||||
{
|
||||
$descriptor = $this->entityNames[$matches[1]] ?? NULL;
|
||||
if($descriptor && $descriptor[0] === ((int) $dual)) {
|
||||
$repoClass = 'openvk\Web\Models\Repositories\\' . $descriptor[1] . 's';
|
||||
$repoInst = new $repoClass;
|
||||
$entity = $repoInst->{$descriptor[2]}(...array_map(function($x): int {
|
||||
return (int) $x;
|
||||
}, array_slice($matches, 2)));
|
||||
|
||||
if($entity) {
|
||||
if($needImage) {
|
||||
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
|
||||
$thumbnailMethod = $descriptor[3];
|
||||
if(!$thumbnailMethod)
|
||||
return "$serverUrl/assets/packages/static/openvk/img/entity_nopic.png";
|
||||
else
|
||||
return $entity->{$thumbnailMethod}();
|
||||
} else {
|
||||
if(in_array('openvk\Web\Models\Entities\ILinkable', class_implements($entity)))
|
||||
return $entity->getOVKLink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
private function resolvePageLinks(): string
|
||||
{
|
||||
return preg_replace_callback('%\[\[((?:\p{L}\p{M}?|[ 0-9\-\'_\/#])+)(\|(\p{L}\p{M}?|[ 0-9\-\'_\/]))?\]\]%u', function(array $matches): string {
|
||||
$gid = $this->page->getOwner()->getId() * -1;
|
||||
$page = $this->repo->getByOwnerAndTitle($gid, $matches[1]);
|
||||
$title = $matches[3] ?? $matches[1];
|
||||
$nTitle = htmlentities($title);
|
||||
if(!$page)
|
||||
return "\"(nonexistent)$nTitle\":/pages?elid=0&gid=" . ($gid * -1) . "&title=" . rawurlencode($title);
|
||||
else
|
||||
return "\"$nTitle\":/page" . $page->getPrettyId();
|
||||
}, $this->page->getSource());
|
||||
}
|
||||
|
||||
private function parseVariables(): string
|
||||
{
|
||||
$html = $this->resolvePageLinks();
|
||||
return preg_replace_callback('%(?<!\\\\)\?(\$|#)([A-z_]|[A-z_][A-z_0-9]|(?:[A-z_][A-z_\-\\\'0-9]+[A-z_0-9]))\?%', function(array $matches): string {
|
||||
return (string) (($matches[1] === "$" ? $this->ctx : $this->vars)[$matches[2]] ?? "<b>Notice: Unknown variable $matches[2]</b><br/>");
|
||||
}, $html);
|
||||
}
|
||||
|
||||
private function parseOvkTemplates(): string
|
||||
{
|
||||
$html = $this->parseVariables();
|
||||
|
||||
if($this->counter < 5) {
|
||||
return preg_replace_callback('%{{Template:\-([0-9]++)_([0-9]++)\|?([^{}]++)}}%', function(array $matches): string {
|
||||
$params = [];
|
||||
[, $public, $page, $paramStr] = $matches;
|
||||
|
||||
$tplPage = $this->repo->getByOwnerAndVID(-1 * $public, (int) $page);
|
||||
if(!$tplPage)
|
||||
return "<b>Notice: No template at public$public/$page</b><br/>";
|
||||
|
||||
foreach(explode("|", $paramStr) as $kvPair) {
|
||||
$kvPair = explode("=", $kvPair);
|
||||
if(sizeof($kvPair) != 2)
|
||||
continue;
|
||||
|
||||
$params[$kvPair[0]] = $kvPair[1];
|
||||
}
|
||||
bdump($params);
|
||||
$parser = new Parser($tplPage, $this->repo, $this->depth, $params, $this->ctx);
|
||||
return $parser->asHTML();
|
||||
}, $html);
|
||||
} else {
|
||||
return "<b>Notice: Refusing to include template due to high indirection level (6)</b><br/>";
|
||||
}
|
||||
}
|
||||
|
||||
private function parseTextile(): string
|
||||
{
|
||||
return (new Textile\Parser)->parse($this->parseOvkTemplates());
|
||||
}
|
||||
|
||||
private function parseOvkIncludes(): string
|
||||
{
|
||||
$html = new \DOMDocument();
|
||||
$html->loadHTML("<?xml encoding=\"UTF-8\">" . $this->parseTextile());
|
||||
foreach($html->getElementsByTagName("a") as $link) {
|
||||
$href = $link->getAttribute("href");
|
||||
if(preg_match('%^#([a-z]++)(\-?[0-9]++)_([0-9]++)#$%', $href, $matches))
|
||||
$link->setAttribute("href", $this->resolveEntityURL($matches, true, false) ?? "unknown");
|
||||
else if(preg_match('%^#([a-z]++)(\-?[0-9]++)#$%', $href, $matches))
|
||||
$link->setAttribute("href", $this->resolveEntityURL($matches, false, false) ?? "unknown");
|
||||
}
|
||||
|
||||
foreach($html->getElementsByTagName("img") as $pic) {
|
||||
$src = $pic->getAttribute("src");
|
||||
if(preg_match('%^#([a-z]++)(\-?[0-9]++)_([0-9]++)#$%', $src, $matches))
|
||||
$pic->setAttribute("src", $this->resolveEntityURL($matches, true, true) ?? "unknown");
|
||||
else if(preg_match('%^#([a-z]++)(\-?[0-9]++)#$%', $src, $matches))
|
||||
$pic->setAttribute("src", $this->resolveEntityURL($matches, false, true) ?? "unknown");
|
||||
}
|
||||
|
||||
return $html->saveHTML();
|
||||
}
|
||||
|
||||
function asHTML(): string
|
||||
{
|
||||
$purifier = $this->getPurifier();
|
||||
return $purifier->purify($this->parseOvkIncludes());
|
||||
}
|
||||
}
|
39
Web/Models/Entities/WikiPage/WikiPage.php
Normal file
39
Web/Models/Entities/WikiPage/WikiPage.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities\WikiPage;
|
||||
use openvk\Web\Models\Repositories\WikiPages;
|
||||
use openvk\Web\Models\Entities\Postable;
|
||||
|
||||
class WikiPage extends Postable {
|
||||
protected $tableName = "wikipages";
|
||||
|
||||
function getTitle(): string
|
||||
{
|
||||
return $this->getRecord()->title;
|
||||
}
|
||||
|
||||
function getSource(): string
|
||||
{
|
||||
return $this->getRecord()->source;
|
||||
}
|
||||
|
||||
function getHitCounter(): int
|
||||
{
|
||||
return $this->getRecord()->hits;
|
||||
}
|
||||
|
||||
function getText(array $ctx = []): string
|
||||
{
|
||||
$counter = 0;
|
||||
$parser = new Parser($this, new WikiPages, $counter, ["firstInclusion" => "yes"], array_merge([
|
||||
"time" => time(),
|
||||
], $ctx));
|
||||
|
||||
return $parser->asHTML();
|
||||
}
|
||||
|
||||
function view(): void
|
||||
{
|
||||
$this->stateChanges("hits", $this->getRecord()->hits + 1);
|
||||
$this->save();
|
||||
}
|
||||
}
|
39
Web/Models/Repositories/WikiPages.php
Normal file
39
Web/Models/Repositories/WikiPages.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
use openvk\Web\Models\Entities\WikiPage;
|
||||
use Nette\Database\Table\ActiveRow;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
|
||||
class WikiPages
|
||||
{
|
||||
private $context;
|
||||
private $wp;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->context = DatabaseConnection::i()->getContext();
|
||||
$this->wp = $this->context->table("wikipages");
|
||||
}
|
||||
|
||||
private function toWikiPage(?ActiveRow $ar): ?WikiPage
|
||||
{
|
||||
return is_null($ar) ? NULL : new WikiPage($ar);
|
||||
}
|
||||
|
||||
function get(int $id): ?WikiPage
|
||||
{
|
||||
return $this->toWikiPage($this->wp->get($id));
|
||||
}
|
||||
|
||||
function getByOwnerAndVID(int $owner, int $note): ?WikiPage
|
||||
{
|
||||
$wp = (clone $this->wp)->where(['owner' => $owner, 'virtual_id' => $note])->fetch();
|
||||
return $this->toWikiPage($wp);
|
||||
}
|
||||
|
||||
function getByOwnerAndTitle(int $owner, string $title): ?WikiPage
|
||||
{
|
||||
$wp = (clone $this->wp)->where(['owner' => $owner, 'title' => $title])->fetch();
|
||||
return $this->toWikiPage($wp);
|
||||
}
|
||||
}
|
|
@ -203,6 +203,7 @@ final class GroupPresenter extends OpenVKPresenter
|
|||
$club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about"));
|
||||
$club->setShortcode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode"));
|
||||
$club->setWall(empty($this->postParam("wall")) ? 0 : 1);
|
||||
$club->setWikiEnabled(empty($this->postParam("wiki")) ? false : true);
|
||||
$club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display"));
|
||||
|
||||
$website = $this->postParam("website") ?? "";
|
||||
|
|
112
Web/Presenters/WikiPresenter.php
Normal file
112
Web/Presenters/WikiPresenter.php
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Repositories\{Clubs, WikiPages};
|
||||
use openvk\Web\Models\Entities\WikiPage;
|
||||
|
||||
final class WikiPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $groups;
|
||||
private $pages;
|
||||
|
||||
function __construct(Clubs $groups, WikiPages $pages)
|
||||
{
|
||||
$this->groups = $groups;
|
||||
$this->pages = $pages;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
function renderView(int $owner, int $page)
|
||||
{
|
||||
$page = $this->pages->getByOwnerAndVID((int) $owner, (int) $page);
|
||||
if(!$page || !$page->getOwner()->containsWiki())
|
||||
$this->notFound();
|
||||
|
||||
$this->template->oURL = $page->getOwner()->getURL();
|
||||
$this->template->oName = $page->getOwner()->getCanonicalName();
|
||||
$this->template->title = $page->getTitle();
|
||||
$this->template->html = $page->getText([
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
function renderSource(int $owner, int $page)
|
||||
{
|
||||
$page = $this->pages->getByOwnerAndVID((int) $owner, (int) $page);
|
||||
if(!$page)
|
||||
$this->flashFail("err", tr("error"), tr("page_id_invalid"));
|
||||
|
||||
$src = $page->getSource();
|
||||
header("Content-Type: text/plain");
|
||||
header("Content-Length: " . strlen($src));
|
||||
header("Pragma: no-cache");
|
||||
exit($src);
|
||||
}
|
||||
|
||||
function renderEdit()
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
if(is_null($groupId = $this->queryParam("gid")))
|
||||
$this->flashFail("err", tr("error"), tr("group_id_invalid"));
|
||||
|
||||
$group = $this->groups->get((int) $groupId);
|
||||
if(!$group)
|
||||
$this->flashFail("err", tr("error"), tr("group_id_invalid"));
|
||||
else if(!$group->canBeModifiedBy($this->user->identity))
|
||||
$this->flashFail("err", tr("error"), tr("access_error"));
|
||||
|
||||
$page;
|
||||
$pageId = $this->queryParam("elid");
|
||||
$title = $this->requestParam("title");
|
||||
if(!is_null($pageId) && $pageId > 0) {
|
||||
$page = $this->pages->getByOwnerAndVID($groupId * -1, (int) $pageId);
|
||||
if(!$page)
|
||||
$this->flashFail("err", tr("error"), tr("page_id_invalid"));
|
||||
} else if(!is_null($title)) {
|
||||
$page = $this->pages->getByOwnerAndTitle($groupId * -1, $title);
|
||||
} else {
|
||||
$this->flashFail("err", tr("error"), tr("page_id_invalid"));
|
||||
}
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
if($this->postParam("elid") == 0) {
|
||||
$page = new WikiPage;
|
||||
$page->setOwner($groupId * -1);
|
||||
$page->setTitle($title);
|
||||
}
|
||||
|
||||
if($this->postParam("elid") != 0 && $title !== $page->getTitle()) {
|
||||
if(!is_null($this->pages->getByOwnerAndTitle($groupId * -1, $title)))
|
||||
$this->flashFail("err", tr("error"), tr("article_already_exists"));
|
||||
|
||||
$page->setTitle($title);
|
||||
}
|
||||
|
||||
$page->setSource($this->postParam("source"));
|
||||
$page->save();
|
||||
|
||||
$this->flash("succ", tr("succ"), tr("article_saved"));
|
||||
$this->redirect("/pages?gid=$groupId&title=" . rawurlencode($title));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!$page) {
|
||||
$this->template->form = (object) [
|
||||
"pId" => 0,
|
||||
"gId" => $groupId,
|
||||
"title" => $title ?? "",
|
||||
"source" => "",
|
||||
];
|
||||
} else {
|
||||
$this->template->form = (object) [
|
||||
"pId" => $page->getVirtualId(),
|
||||
"gId" => $groupId,
|
||||
"title" => $page->getTitle(),
|
||||
"source" => $page->getSource(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@
|
|||
<input type="file" name="ava" accept="image/*" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_wall}: </span>
|
||||
</td>
|
||||
|
@ -77,6 +77,14 @@
|
|||
<input type="checkbox" name="wall" value="1" {if $club->canPost()}checked{/if}/> {_group_allow_post_for_everyone}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_wiki}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" name="wiki" n:attr="checked => $club->containsWiki()" /> {_enable_wiki}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_group_administrators_list}: </span>
|
||||
|
|
|
@ -41,42 +41,64 @@
|
|||
</table>
|
||||
</div>
|
||||
<div n:if="$club->getFollowersCount() > 0">
|
||||
{var followersCount = $club->getFollowersCount()}
|
||||
{var followersCount = $club->getFollowersCount()}
|
||||
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});">
|
||||
{_participants}
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});">
|
||||
{_participants}
|
||||
</div>
|
||||
<div>
|
||||
<div class="content_subtitle">
|
||||
{tr("participants", $followersCount)}
|
||||
<div style="float:right;">
|
||||
<a href="/club{$club->getId()}/followers">{_all_title}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="content_subtitle">
|
||||
{tr("participants", $followersCount)}
|
||||
<div style="float:right;">
|
||||
<a href="/club{$club->getId()}/followers">{_all_title}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding-left: 5px;">
|
||||
<table
|
||||
n:foreach="$club->getFollowers(1) as $follower"
|
||||
n:class="User"
|
||||
style="text-align:center;display:inline-block;width:62px"
|
||||
cellspacing=4>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{$follower->getURL()}">
|
||||
<img src="{$follower->getAvatarUrl()}" width="50" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{$follower->getURL()}">{$follower->getFirstName()}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="padding-left: 5px;">
|
||||
<table
|
||||
n:foreach="$club->getFollowers(1) as $follower"
|
||||
n:class="User"
|
||||
style="text-align:center;display:inline-block;width:62px"
|
||||
cellspacing='4'>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{$follower->getURL()}">
|
||||
<img src="{$follower->getAvatarUrl()}" width="50" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{$follower->getURL()}">{$follower->getFirstName()}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div n:if="$club->containsWiki()">
|
||||
{var page = $club->getWikiHomePage()}
|
||||
|
||||
<div class="content_title_expanded" onclick="hidePanel(this);">
|
||||
{$page->getTitle()}
|
||||
</div>
|
||||
<div>
|
||||
<div class="content_subtitle">
|
||||
{_wiki_page}
|
||||
<div n:if="$club->canBeModifiedBy($thisUser)" style="float:right;">
|
||||
<a href="/pages?gid={$club->getId()}&elid=1" target="_blank">{_edit}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding-left: 5px;">
|
||||
<article id="userContent" style="overflow-y: scroll; height: 200px;">
|
||||
{$page->getText()|noescape}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
{presenter "openvk!Wall->wallEmbedded", -$club->getId()}
|
||||
|
||||
|
|
42
Web/Presenters/templates/Wiki/Edit.xml
Normal file
42
Web/Presenters/templates/Wiki/Edit.xml
Normal file
|
@ -0,0 +1,42 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}{_create_note}{/block}
|
||||
|
||||
{block header}
|
||||
{_create_note}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<form id="wikiFactory" method="POST">
|
||||
<input type="text" name="title" value="{$form->title}" placeholder="{_name_note}" style="width:603px;" />
|
||||
<br/><br/>
|
||||
<textarea name="source" style="display:none;"></textarea>
|
||||
<div id="editor" style="width:600px;height:300px;border:1px solid grey"></div>
|
||||
|
||||
<input type="hidden" name="elid" value="{$form->pId}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<button class="button">{_save}</button>
|
||||
</form>
|
||||
|
||||
{script "js/node_modules/monaco-editor/min/vs/loader.js"}
|
||||
{script "js/node_modules/requirejs/bin/r.js"}
|
||||
<script>
|
||||
require.config({
|
||||
paths: {
|
||||
'vs': '/assets/packages/static/openvk/js/node_modules/monaco-editor/min/vs'
|
||||
}
|
||||
});
|
||||
require(['vs/editor/editor.main'], function() {
|
||||
window._editor = monaco.editor.create(document.getElementById('editor'), {
|
||||
value: {$form->source},
|
||||
lineNumbers: "on",
|
||||
wordWrap: "on",
|
||||
language: "html"
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector("#wikiFactory").addEventListener("submit", function() {
|
||||
document.querySelector("textarea").value = window._editor.getValue();
|
||||
});
|
||||
</script>
|
||||
{/block}
|
15
Web/Presenters/templates/Wiki/View.xml
Normal file
15
Web/Presenters/templates/Wiki/View.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}{$title} - {$oName}{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$oURL}">{$oName}</a>
|
||||
»
|
||||
{$title}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<article id="userContent" style="min-height: 300pt;">
|
||||
{$html|noescape}
|
||||
</article>
|
||||
{/block}
|
|
@ -18,6 +18,7 @@ services:
|
|||
- openvk\Web\Presenters\SupportPresenter
|
||||
- openvk\Web\Presenters\AdminPresenter
|
||||
- openvk\Web\Presenters\GiftsPresenter
|
||||
- openvk\Web\Presenters\WikiPresenter
|
||||
- openvk\Web\Presenters\MessengerPresenter
|
||||
- openvk\Web\Presenters\ThemepacksPresenter
|
||||
- openvk\Web\Presenters\VKAPIPresenter
|
||||
|
@ -36,4 +37,5 @@ services:
|
|||
- openvk\Web\Models\Repositories\IPs
|
||||
- openvk\Web\Models\Repositories\Vouchers
|
||||
- openvk\Web\Models\Repositories\Gifts
|
||||
- openvk\Web\Models\Repositories\WikiPages
|
||||
- openvk\Web\Models\Repositories\ContentSearchRepository
|
||||
|
|
|
@ -215,6 +215,12 @@ routes:
|
|||
handler: "Gifts->userGifts"
|
||||
- url: "/gifts"
|
||||
handler: "Gifts->stub"
|
||||
- url: "/pages"
|
||||
handler: "Wiki->edit"
|
||||
- url: "/page{num}_{num}"
|
||||
handler: "Wiki->view"
|
||||
- url: "/page{num}_{num}.textile"
|
||||
handler: "Wiki->source"
|
||||
- url: "/admin"
|
||||
handler: "Admin->index"
|
||||
- url: "/admin/users"
|
||||
|
|
|
@ -1598,3 +1598,15 @@ body.scrolled .toTop:hover {
|
|||
margin: 5px;
|
||||
border: 1px solid #C0CAD5;
|
||||
}
|
||||
|
||||
article#userContent {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
article#userContent a {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
article#userContent a.nonexistent {
|
||||
color: #d90000;
|
||||
}
|
|
@ -234,7 +234,7 @@ return (function() {
|
|||
|
||||
define("nullptr", NULL);
|
||||
define("OPENVK_DEFAULT_INSTANCE_NAME", "OpenVK", false);
|
||||
define("OPENVK_VERSION", "Altair Preview ($ver)", false);
|
||||
define("OPENVK_VERSION", "Altair Preview ($ver-wiki)", false);
|
||||
define("OPENVK_DEFAULT_PER_PAGE", 10, false);
|
||||
define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF", false);
|
||||
});
|
||||
|
|
17
install/sqls/00014-wikipages.sql
Normal file
17
install/sqls/00014-wikipages.sql
Normal file
|
@ -0,0 +1,17 @@
|
|||
CREATE TABLE IF NOT EXISTS `wikipages` (
|
||||
`id` bigint(20) unsigned PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
||||
`owner` bigint(20) NOT NULL,
|
||||
`virtual_id` bigint(20) NOT NULL,
|
||||
`created` bigint(20) NOT NULL,
|
||||
`edited` bigint(20) DEFAULT NULL,
|
||||
`title` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`source` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`hits` bigint(20) NOT NULL DEFAULT 0,
|
||||
`anonymous` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`deleted` tinyint(4) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
ALTER TABLE `wikipages` ADD INDEX( `owner`, `virtual_id`);
|
||||
ALTER TABLE `wikipages` ADD UNIQUE( `owner`, `title`);
|
||||
|
||||
ALTER TABLE `groups` ADD `pages` BOOLEAN NOT NULL DEFAULT FALSE AFTER `wall`;
|
Loading…
Reference in a new issue