mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-16 19:06:45 +03:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7b01ae53d | ||
|
|
d704e1dbba | ||
|
|
393f6047f2 | ||
|
|
d94fc5b694 | ||
|
|
f06770d91d | ||
|
|
ef2ff5e093 | ||
|
|
7caed3b0db | ||
|
|
11960d9d3a | ||
|
|
bbd8fff021 | ||
|
|
ec17bd8608 | ||
|
|
0dbb8babee | ||
|
|
b14e9fc619 | ||
|
|
63c6d3478d | ||
|
|
781f0e7887 | ||
|
|
23e014cb25 | ||
|
|
5b64358ef1 | ||
|
|
56df64063d | ||
|
|
3f81eba13b | ||
|
|
7973412c29 | ||
|
|
f83de5f834 | ||
|
|
f2ceba978a | ||
|
|
96c074bb56 | ||
|
|
f8ad820281 | ||
|
|
632ecc668f | ||
|
|
92d393537c | ||
|
|
42976ca48c | ||
|
|
d05e85efa9 | ||
|
|
547e117760 | ||
|
|
50a5d3c546 | ||
|
|
3fd82200cc | ||
|
|
7215392784 | ||
|
|
8a9a8dfae5 | ||
|
|
c44314def3 | ||
|
|
8b899a9cf0 | ||
|
|
0ebdfa4825 | ||
|
|
32a06f119b | ||
|
|
ec30864ce5 | ||
|
|
6bc72e157a |
11
.github/ISSUE_TEMPLATE.md
vendored
Normal file
11
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
### For Feature Requests
|
||||
Desired Feature:
|
||||
|
||||
### For Bug Reports
|
||||
PHP Version:
|
||||
|
||||
MySQL Version:
|
||||
|
||||
Expected Behavior:
|
||||
|
||||
Actual Behavior:
|
||||
12
.travis.yml
12
.travis.yml
@@ -6,8 +6,6 @@ php:
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
- node_modules
|
||||
- $HOME/.composer/cache
|
||||
|
||||
addons:
|
||||
@@ -17,19 +15,17 @@ addons:
|
||||
- mysql-client-core-5.6
|
||||
- mysql-client-5.6
|
||||
|
||||
before_install:
|
||||
- npm install -g npm@latest
|
||||
|
||||
before_script:
|
||||
- mysql -u root -e 'create database `bookstack-test`;'
|
||||
- composer config -g github-oauth.github.com $GITHUB_ACCESS_TOKEN
|
||||
- phpenv config-rm xdebug.ini
|
||||
- composer self-update
|
||||
- composer dump-autoload --no-interaction
|
||||
- composer install --prefer-dist --no-interaction
|
||||
- npm install
|
||||
- ./node_modules/.bin/gulp
|
||||
- php artisan clear-compiled -n
|
||||
- php artisan optimize -n
|
||||
- php artisan migrate --force -n --database=mysql_testing
|
||||
- php artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit
|
||||
- phpunit
|
||||
@@ -167,7 +167,8 @@ class Entity extends Ownable
|
||||
foreach ($terms as $key => $term) {
|
||||
$term = htmlentities($term, ENT_QUOTES);
|
||||
$term = preg_replace('/[+\-><\(\)~*\"@]+/', ' ', $term);
|
||||
if (preg_match('/\s/', $term)) {
|
||||
if (preg_match('/".*?"/', $term)) {
|
||||
$term = str_replace('"', '', $term);
|
||||
$exactTerms[] = '%' . $term . '%';
|
||||
$term = '"' . $term . '"';
|
||||
} else {
|
||||
@@ -206,5 +207,5 @@ class Entity extends Ownable
|
||||
|
||||
return $search->orderBy($orderBy, 'desc');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -47,19 +47,44 @@ class Handler extends ExceptionHandler
|
||||
{
|
||||
// Handle notify exceptions which will redirect to the
|
||||
// specified location then show a notification message.
|
||||
if ($e instanceof NotifyException) {
|
||||
session()->flash('error', $e->message);
|
||||
if ($this->isExceptionType($e, NotifyException::class)) {
|
||||
session()->flash('error', $this->getOriginalMessage($e));
|
||||
return redirect($e->redirectLocation);
|
||||
}
|
||||
|
||||
// Handle pretty exceptions which will show a friendly application-fitting page
|
||||
// Which will include the basic message to point the user roughly to the cause.
|
||||
if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) {
|
||||
$message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage();
|
||||
if ($this->isExceptionType($e, PrettyException::class) && !config('app.debug')) {
|
||||
$message = $this->getOriginalMessage($e);
|
||||
$code = ($e->getCode() === 0) ? 500 : $e->getCode();
|
||||
return response()->view('errors/' . $code, ['message' => $message], $code);
|
||||
}
|
||||
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the exception chain to compare against the original exception type.
|
||||
* @param Exception $e
|
||||
* @param $type
|
||||
* @return bool
|
||||
*/
|
||||
protected function isExceptionType(Exception $e, $type) {
|
||||
do {
|
||||
if (is_a($e, $type)) return true;
|
||||
} while ($e = $e->getPrevious());
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get original exception message.
|
||||
* @param Exception $e
|
||||
* @return string
|
||||
*/
|
||||
protected function getOriginalMessage(Exception $e) {
|
||||
do {
|
||||
$message = $e->getMessage();
|
||||
} while ($e = $e->getPrevious());
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
<?php namespace BookStack\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class PrettyException extends Exception {}
|
||||
class PrettyException extends \Exception {}
|
||||
@@ -3,7 +3,6 @@
|
||||
use Activity;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use BookStack\Http\Requests;
|
||||
use BookStack\Repos\BookRepo;
|
||||
use BookStack\Repos\ChapterRepo;
|
||||
@@ -180,21 +179,31 @@ class BookController extends Controller
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
|
||||
$sortedBooks = [];
|
||||
// Sort pages and chapters
|
||||
$sortedBooks = [];
|
||||
$updatedModels = collect();
|
||||
$sortMap = json_decode($request->get('sort-tree'));
|
||||
$defaultBookId = $book->id;
|
||||
foreach ($sortMap as $index => $bookChild) {
|
||||
$id = $bookChild->id;
|
||||
|
||||
// Loop through contents of provided map and update entities accordingly
|
||||
foreach ($sortMap as $bookChild) {
|
||||
$priority = $bookChild->sort;
|
||||
$id = intval($bookChild->id);
|
||||
$isPage = $bookChild->type == 'page';
|
||||
$bookId = $this->bookRepo->exists($bookChild->book) ? $bookChild->book : $defaultBookId;
|
||||
$bookId = $this->bookRepo->exists($bookChild->book) ? intval($bookChild->book) : $defaultBookId;
|
||||
$chapterId = ($isPage && $bookChild->parentChapter === false) ? 0 : intval($bookChild->parentChapter);
|
||||
$model = $isPage ? $this->pageRepo->getById($id) : $this->chapterRepo->getById($id);
|
||||
$isPage ? $this->pageRepo->changeBook($bookId, $model) : $this->chapterRepo->changeBook($bookId, $model);
|
||||
$model->priority = $index;
|
||||
if ($isPage) {
|
||||
$model->chapter_id = ($bookChild->parentChapter === false) ? 0 : $bookChild->parentChapter;
|
||||
|
||||
// Update models only if there's a change in parent chain or ordering.
|
||||
if ($model->priority !== $priority || $model->book_id !== $bookId || ($isPage && $model->chapter_id !== $chapterId)) {
|
||||
$isPage ? $this->pageRepo->changeBook($bookId, $model) : $this->chapterRepo->changeBook($bookId, $model);
|
||||
$model->priority = $priority;
|
||||
if ($isPage) $model->chapter_id = $chapterId;
|
||||
$model->save();
|
||||
$updatedModels->push($model);
|
||||
}
|
||||
$model->save();
|
||||
|
||||
// Store involved books to be sorted later
|
||||
if (!in_array($bookId, $sortedBooks)) {
|
||||
$sortedBooks[] = $bookId;
|
||||
}
|
||||
@@ -203,10 +212,12 @@ class BookController extends Controller
|
||||
// Add activity for books
|
||||
foreach ($sortedBooks as $bookId) {
|
||||
$updatedBook = $this->bookRepo->getById($bookId);
|
||||
$this->bookRepo->updateBookPermissions($updatedBook);
|
||||
Activity::add($updatedBook, 'book_sort', $updatedBook->id);
|
||||
}
|
||||
|
||||
// Update permissions on changed models
|
||||
$this->bookRepo->buildJointPermissions($updatedModels);
|
||||
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ class ChapterController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
$this->chapterRepo->changeBook($parent->id, $chapter);
|
||||
$this->chapterRepo->changeBook($parent->id, $chapter, true);
|
||||
Activity::add($chapter, 'chapter_move', $chapter->book->id);
|
||||
session()->flash('success', sprintf('Chapter moved to "%s"', $parent->name));
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
class PageRevision extends Model
|
||||
{
|
||||
protected $fillable = ['name', 'html', 'text', 'markdown'];
|
||||
protected $fillable = ['name', 'html', 'text', 'markdown', 'summary'];
|
||||
|
||||
/**
|
||||
* Get the user that created the page revision
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<?php
|
||||
<?php namespace BookStack\Providers;
|
||||
|
||||
namespace BookStack\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use BookStack\User;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use Alpha\B;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use BookStack\Book;
|
||||
use Views;
|
||||
@@ -173,15 +174,6 @@ class BookRepo extends EntityRepo
|
||||
$book->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias method to update the book jointPermissions in the PermissionService.
|
||||
* @param Book $book
|
||||
*/
|
||||
public function updateBookPermissions(Book $book)
|
||||
{
|
||||
$this->permissionService->buildJointPermissionsForEntity($book);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next child element priority.
|
||||
* @param Book $book
|
||||
|
||||
@@ -195,11 +195,12 @@ class ChapterRepo extends EntityRepo
|
||||
|
||||
/**
|
||||
* Changes the book relation of this chapter.
|
||||
* @param $bookId
|
||||
* @param $bookId
|
||||
* @param Chapter $chapter
|
||||
* @param bool $rebuildPermissions
|
||||
* @return Chapter
|
||||
*/
|
||||
public function changeBook($bookId, Chapter $chapter)
|
||||
public function changeBook($bookId, Chapter $chapter, $rebuildPermissions = false)
|
||||
{
|
||||
$chapter->book_id = $bookId;
|
||||
// Update related activity
|
||||
@@ -213,9 +214,12 @@ class ChapterRepo extends EntityRepo
|
||||
foreach ($chapter->pages as $page) {
|
||||
$this->pageRepo->changeBook($bookId, $page);
|
||||
}
|
||||
// Update permissions
|
||||
$chapter->load('book');
|
||||
$this->permissionService->buildJointPermissionsForEntity($chapter->book);
|
||||
|
||||
// Update permissions if applicable
|
||||
if ($rebuildPermissions) {
|
||||
$chapter->load('book');
|
||||
$this->permissionService->buildJointPermissionsForEntity($chapter->book);
|
||||
}
|
||||
|
||||
return $chapter;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use BookStack\Entity;
|
||||
use BookStack\Page;
|
||||
use BookStack\Services\PermissionService;
|
||||
use BookStack\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class EntityRepo
|
||||
@@ -168,15 +169,16 @@ class EntityRepo
|
||||
* @param $termString
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareSearchTerms($termString)
|
||||
public function prepareSearchTerms($termString)
|
||||
{
|
||||
$termString = $this->cleanSearchTermString($termString);
|
||||
preg_match_all('/"(.*?)"/', $termString, $matches);
|
||||
preg_match_all('/(".*?")/', $termString, $matches);
|
||||
$terms = [];
|
||||
if (count($matches[1]) > 0) {
|
||||
$terms = $matches[1];
|
||||
foreach ($matches[1] as $match) {
|
||||
$terms[] = $match;
|
||||
}
|
||||
$termString = trim(preg_replace('/"(.*?)"/', '', $termString));
|
||||
} else {
|
||||
$terms = [];
|
||||
}
|
||||
if (!empty($termString)) $terms = array_merge($terms, explode(' ', $termString));
|
||||
return $terms;
|
||||
@@ -259,6 +261,15 @@ class EntityRepo
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias method to update the book jointPermissions in the PermissionService.
|
||||
* @param Collection $collection collection on entities
|
||||
*/
|
||||
public function buildJointPermissions(Collection $collection)
|
||||
{
|
||||
$this->permissionService->buildJointPermissionsForEntities($collection);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -157,6 +157,8 @@ class PageRepo extends EntityRepo
|
||||
$draftPage->draft = false;
|
||||
|
||||
$draftPage->save();
|
||||
$this->saveRevision($draftPage, 'Initial Publish');
|
||||
|
||||
return $draftPage;
|
||||
}
|
||||
|
||||
@@ -308,10 +310,9 @@ class PageRepo extends EntityRepo
|
||||
*/
|
||||
public function updatePage(Page $page, $book_id, $input)
|
||||
{
|
||||
// Save a revision before updating
|
||||
if ($page->html !== $input['html'] || $page->name !== $input['name']) {
|
||||
$this->saveRevision($page);
|
||||
}
|
||||
// Hold the old details to compare later
|
||||
$oldHtml = $page->html;
|
||||
$oldName = $page->name;
|
||||
|
||||
// Prevent slug being updated if no name change
|
||||
if ($page->name !== $input['name']) {
|
||||
@@ -335,6 +336,11 @@ class PageRepo extends EntityRepo
|
||||
// Remove all update drafts for this user & page.
|
||||
$this->userUpdateDraftsQuery($page, $userId)->delete();
|
||||
|
||||
// Save a revision after updating
|
||||
if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
|
||||
$this->saveRevision($page, $input['summary']);
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
@@ -360,9 +366,10 @@ class PageRepo extends EntityRepo
|
||||
/**
|
||||
* Saves a page revision into the system.
|
||||
* @param Page $page
|
||||
* @param null|string $summary
|
||||
* @return $this
|
||||
*/
|
||||
public function saveRevision(Page $page)
|
||||
public function saveRevision(Page $page, $summary = null)
|
||||
{
|
||||
$revision = $this->pageRevision->fill($page->toArray());
|
||||
if (setting('app-editor') !== 'markdown') $revision->markdown = '';
|
||||
@@ -372,6 +379,7 @@ class PageRepo extends EntityRepo
|
||||
$revision->created_by = auth()->user()->id;
|
||||
$revision->created_at = $page->updated_at;
|
||||
$revision->type = 'version';
|
||||
$revision->summary = $summary;
|
||||
$revision->save();
|
||||
// Clear old revisions
|
||||
if ($this->pageRevision->where('page_id', '=', $page->id)->count() > 50) {
|
||||
|
||||
@@ -48,11 +48,13 @@ class ExportService
|
||||
foreach ($imageTagsOutput[0] as $index => $imgMatch) {
|
||||
$oldImgString = $imgMatch;
|
||||
$srcString = $imageTagsOutput[2][$index];
|
||||
if (strpos(trim($srcString), 'http') !== 0) {
|
||||
$pathString = public_path($srcString);
|
||||
$isLocal = strpos(trim($srcString), 'http') !== 0;
|
||||
if ($isLocal) {
|
||||
$pathString = public_path(trim($srcString, '/'));
|
||||
} else {
|
||||
$pathString = $srcString;
|
||||
}
|
||||
if ($isLocal && !file_exists($pathString)) continue;
|
||||
$imageContent = file_get_contents($pathString);
|
||||
$imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent);
|
||||
$newImageString = str_replace($srcString, $imageEncoded, $oldImgString);
|
||||
|
||||
@@ -95,6 +95,7 @@ class ImageService
|
||||
|
||||
try {
|
||||
$storage->put($fullPath, $imageData);
|
||||
$storage->setVisibility($fullPath, 'public');
|
||||
} catch (Exception $e) {
|
||||
throw new ImageUploadException('Image Path ' . $fullPath . ' is not writable by the server.');
|
||||
}
|
||||
@@ -167,6 +168,7 @@ class ImageService
|
||||
|
||||
$thumbData = (string)$thumb->encode();
|
||||
$storage->put($thumbFilePath, $thumbData);
|
||||
$storage->setVisibility($thumbFilePath, 'public');
|
||||
$this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72);
|
||||
|
||||
return $this->getPublicUrl($thumbFilePath);
|
||||
@@ -257,9 +259,15 @@ class ImageService
|
||||
$storageUrl = config('filesystems.url');
|
||||
|
||||
// Get the standard public s3 url if s3 is set as storage type
|
||||
// Uses the nice, short URL if bucket name has no periods in otherwise the longer
|
||||
// region-based url will be used to prevent http issues.
|
||||
if ($storageUrl == false && config('filesystems.default') === 's3') {
|
||||
$storageDetails = config('filesystems.disks.s3');
|
||||
$storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];
|
||||
if (strpos($storageDetails['bucket'], '.') === false) {
|
||||
$storageUrl = 'https://' . $storageDetails['bucket'] . '.s3.amazonaws.com';
|
||||
} else {
|
||||
$storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->storageUrl = $storageUrl;
|
||||
@@ -269,4 +277,4 @@ class ImageService
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use BookStack\Ownable;
|
||||
use BookStack\Page;
|
||||
use BookStack\Role;
|
||||
use BookStack\User;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class PermissionService
|
||||
{
|
||||
@@ -25,6 +25,8 @@ class PermissionService
|
||||
protected $jointPermission;
|
||||
protected $role;
|
||||
|
||||
protected $entityCache;
|
||||
|
||||
/**
|
||||
* PermissionService constructor.
|
||||
* @param JointPermission $jointPermission
|
||||
@@ -48,6 +50,57 @@ class PermissionService
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the local entity cache and ensure it's empty
|
||||
*/
|
||||
protected function readyEntityCache()
|
||||
{
|
||||
$this->entityCache = [
|
||||
'books' => collect(),
|
||||
'chapters' => collect()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a book via ID, Checks local cache
|
||||
* @param $bookId
|
||||
* @return Book
|
||||
*/
|
||||
protected function getBook($bookId)
|
||||
{
|
||||
if (isset($this->entityCache['books']) && $this->entityCache['books']->has($bookId)) {
|
||||
return $this->entityCache['books']->get($bookId);
|
||||
}
|
||||
|
||||
$book = $this->book->find($bookId);
|
||||
if ($book === null) $book = false;
|
||||
if (isset($this->entityCache['books'])) {
|
||||
$this->entityCache['books']->put($bookId, $book);
|
||||
}
|
||||
|
||||
return $book;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a chapter via ID, Checks local cache
|
||||
* @param $chapterId
|
||||
* @return Book
|
||||
*/
|
||||
protected function getChapter($chapterId)
|
||||
{
|
||||
if (isset($this->entityCache['chapters']) && $this->entityCache['chapters']->has($chapterId)) {
|
||||
return $this->entityCache['chapters']->get($chapterId);
|
||||
}
|
||||
|
||||
$chapter = $this->chapter->find($chapterId);
|
||||
if ($chapter === null) $chapter = false;
|
||||
if (isset($this->entityCache['chapters'])) {
|
||||
$this->entityCache['chapters']->put($chapterId, $chapter);
|
||||
}
|
||||
|
||||
return $chapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the roles for the current user;
|
||||
* @return array|bool
|
||||
@@ -76,6 +129,7 @@ class PermissionService
|
||||
public function buildJointPermissions()
|
||||
{
|
||||
$this->jointPermission->truncate();
|
||||
$this->readyEntityCache();
|
||||
|
||||
// Get all roles (Should be the most limited dimension)
|
||||
$roles = $this->role->with('permissions')->get();
|
||||
@@ -97,7 +151,7 @@ class PermissionService
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the entity jointPermissions for a particular entity.
|
||||
* Rebuild the entity jointPermissions for a particular entity.
|
||||
* @param Entity $entity
|
||||
*/
|
||||
public function buildJointPermissionsForEntity(Entity $entity)
|
||||
@@ -116,6 +170,17 @@ class PermissionService
|
||||
$this->createManyJointPermissions($entities, $roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the entity jointPermissions for a collection of entities.
|
||||
* @param Collection $entities
|
||||
*/
|
||||
public function buildJointPermissionsForEntities(Collection $entities)
|
||||
{
|
||||
$roles = $this->role->with('jointPermissions')->get();
|
||||
$this->deleteManyJointPermissionsForEntities($entities);
|
||||
$this->createManyJointPermissions($entities, $roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the entity jointPermissions for a particular role.
|
||||
* @param Role $role
|
||||
@@ -177,9 +242,14 @@ class PermissionService
|
||||
*/
|
||||
protected function deleteManyJointPermissionsForEntities($entities)
|
||||
{
|
||||
$query = $this->jointPermission->newQuery();
|
||||
foreach ($entities as $entity) {
|
||||
$entity->jointPermissions()->delete();
|
||||
$query->orWhere(function($query) use ($entity) {
|
||||
$query->where('entity_id', '=', $entity->id)
|
||||
->where('entity_type', '=', $entity->getMorphClass());
|
||||
});
|
||||
}
|
||||
$query->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,6 +259,7 @@ class PermissionService
|
||||
*/
|
||||
protected function createManyJointPermissions($entities, $roles)
|
||||
{
|
||||
$this->readyEntityCache();
|
||||
$jointPermissions = [];
|
||||
foreach ($entities as $entity) {
|
||||
foreach ($roles as $role) {
|
||||
@@ -248,8 +319,9 @@ class PermissionService
|
||||
} elseif ($entity->isA('chapter')) {
|
||||
|
||||
if (!$entity->restricted) {
|
||||
$hasExplicitAccessToBook = $entity->book->hasActiveRestriction($role->id, $restrictionAction);
|
||||
$hasPermissiveAccessToBook = !$entity->book->restricted;
|
||||
$book = $this->getBook($entity->book_id);
|
||||
$hasExplicitAccessToBook = $book->hasActiveRestriction($role->id, $restrictionAction);
|
||||
$hasPermissiveAccessToBook = !$book->restricted;
|
||||
return $this->createJointPermissionDataArray($entity, $role, $action,
|
||||
($hasExplicitAccessToBook || ($roleHasPermission && $hasPermissiveAccessToBook)),
|
||||
($hasExplicitAccessToBook || ($roleHasPermissionOwn && $hasPermissiveAccessToBook)));
|
||||
@@ -261,11 +333,14 @@ class PermissionService
|
||||
} elseif ($entity->isA('page')) {
|
||||
|
||||
if (!$entity->restricted) {
|
||||
$hasExplicitAccessToBook = $entity->book->hasActiveRestriction($role->id, $restrictionAction);
|
||||
$hasPermissiveAccessToBook = !$entity->book->restricted;
|
||||
$hasExplicitAccessToChapter = $entity->chapter && $entity->chapter->hasActiveRestriction($role->id, $restrictionAction);
|
||||
$hasPermissiveAccessToChapter = $entity->chapter && !$entity->chapter->restricted;
|
||||
$acknowledgeChapter = ($entity->chapter && $entity->chapter->restricted);
|
||||
$book = $this->getBook($entity->book_id);
|
||||
$hasExplicitAccessToBook = $book->hasActiveRestriction($role->id, $restrictionAction);
|
||||
$hasPermissiveAccessToBook = !$book->restricted;
|
||||
|
||||
$chapter = $this->getChapter($entity->chapter_id);
|
||||
$hasExplicitAccessToChapter = $chapter && $chapter->hasActiveRestriction($role->id, $restrictionAction);
|
||||
$hasPermissiveAccessToChapter = $chapter && !$chapter->restricted;
|
||||
$acknowledgeChapter = ($chapter && $chapter->restricted);
|
||||
|
||||
$hasExplicitAccessToParents = $acknowledgeChapter ? $hasExplicitAccessToChapter : $hasExplicitAccessToBook;
|
||||
$hasPermissiveAccessToParents = $acknowledgeChapter ? $hasPermissiveAccessToChapter : $hasPermissiveAccessToBook;
|
||||
|
||||
@@ -158,7 +158,7 @@ class SocialAuthService
|
||||
$driver = trim(strtolower($socialDriver));
|
||||
|
||||
if (!in_array($driver, $this->validSocialDrivers)) abort(404, 'Social Driver Not Found');
|
||||
if (!$this->checkDriverConfigured($driver)) throw new SocialDriverNotConfigured;
|
||||
if (!$this->checkDriverConfigured($driver)) throw new SocialDriverNotConfigured("Your {$driver} social settings are not configured correctly.");
|
||||
|
||||
return $driver;
|
||||
}
|
||||
|
||||
@@ -2,33 +2,38 @@
|
||||
|
||||
use BookStack\Ownable;
|
||||
|
||||
if (!function_exists('versioned_asset')) {
|
||||
/**
|
||||
* Get the path to a versioned file.
|
||||
*
|
||||
* @param string $file
|
||||
* @return string
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
function versioned_asset($file)
|
||||
{
|
||||
static $manifest = null;
|
||||
/**
|
||||
* Get the path to a versioned file.
|
||||
*
|
||||
* @param string $file
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
function versioned_asset($file = '')
|
||||
{
|
||||
// Don't require css and JS assets for testing
|
||||
if (config('app.env') === 'testing') return '';
|
||||
|
||||
if (is_null($manifest)) {
|
||||
$manifest = json_decode(file_get_contents(public_path('build/manifest.json')), true);
|
||||
static $manifest = null;
|
||||
$manifestPath = 'build/manifest.json';
|
||||
|
||||
if (is_null($manifest) && file_exists($manifestPath)) {
|
||||
$manifest = json_decode(file_get_contents(public_path($manifestPath)), true);
|
||||
} else if (!file_exists($manifestPath)) {
|
||||
if (config('app.env') !== 'production') {
|
||||
$path = public_path($manifestPath);
|
||||
$error = "No {$path} file found, Ensure you have built the css/js assets using gulp.";
|
||||
} else {
|
||||
$error = "No {$manifestPath} file found, Ensure you are using the release version of BookStack";
|
||||
}
|
||||
|
||||
if (isset($manifest[$file])) {
|
||||
return baseUrl($manifest[$file]);
|
||||
}
|
||||
|
||||
if (file_exists(public_path($file))) {
|
||||
return baseUrl($file);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("File {$file} not defined in asset manifest.");
|
||||
throw new \Exception($error);
|
||||
}
|
||||
|
||||
if (isset($manifest[$file])) {
|
||||
return baseUrl($manifest[$file]);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("File {$file} not defined in asset manifest.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddSummaryToPageRevisions extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('page_revisions', function ($table) {
|
||||
$table->string('summary')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('page_revisions', function ($table) {
|
||||
$table->dropColumn('summary');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
<env name="DB_CONNECTION" value="mysql_testing"/>
|
||||
<env name="MAIL_DRIVER" value="log"/>
|
||||
<env name="AUTH_METHOD" value="standard"/>
|
||||
<env name="DISABLE_EXTERNAL_SERVICES" value="false"/>
|
||||
<env name="DISABLE_EXTERNAL_SERVICES" value="true"/>
|
||||
<env name="LDAP_VERSION" value="3"/>
|
||||
<env name="GITHUB_APP_ID" value="aaaaaaaaaaaaaa"/>
|
||||
<env name="GITHUB_APP_SECRET" value="aaaaaaaaaaaaaa"/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"css/styles.css": "css/styles.css?version=d6312b2",
|
||||
"css/print-styles.css": "css/print-styles.css?version=d6312b2",
|
||||
"js/common.js": "js/common.js?version=d6312b2"
|
||||
"css/styles.css": "css/styles.css?version=10caf1a",
|
||||
"css/print-styles.css": "css/print-styles.css?version=10caf1a",
|
||||
"js/common.js": "js/common.js?version=10caf1a"
|
||||
}
|
||||
2
public/css/export-styles.css
vendored
2
public/css/export-styles.css
vendored
File diff suppressed because one or more lines are too long
2
public/css/styles.css
vendored
2
public/css/styles.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
tinymce.PluginManager.add("advlist",function(a){function b(a,b){var c=[];return tinymce.each(b.split(/[ ,]/),function(a){c.push({text:a.replace(/\-/g," ").replace(/\b\w/g,function(a){return a.toUpperCase()}),data:"default"==a?"":a})}),c}function c(b,c){a.undoManager.transact(function(){var d,e=a.dom,f=a.selection;d=e.getParent(f.getNode(),"ol,ul"),d&&d.nodeName==b&&c!==!1||a.execCommand("UL"==b?"InsertUnorderedList":"InsertOrderedList"),c=c===!1?g[b]:c,g[b]=c,d=e.getParent(f.getNode(),"ol,ul"),d&&(e.setStyle(d,"listStyleType",c?c:null),d.removeAttribute("data-mce-style")),a.focus()})}function d(b){var c=a.dom.getStyle(a.dom.getParent(a.selection.getNode(),"ol,ul"),"listStyleType")||"";b.control.items().each(function(a){a.active(a.settings.data===c)})}var e,f,g={};e=b("OL",a.getParam("advlist_number_styles","default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman")),f=b("UL",a.getParam("advlist_bullet_styles","default,circle,disc,square")),a.addButton("numlist",{type:"splitbutton",tooltip:"Numbered list",menu:e,onshow:d,onselect:function(a){c("OL",a.control.settings.data)},onclick:function(){c("OL",!1)}}),a.addButton("bullist",{type:"splitbutton",tooltip:"Bullet list",menu:f,onshow:d,onselect:function(a){c("UL",a.control.settings.data)},onclick:function(){c("UL",!1)}})});
|
||||
tinymce.PluginManager.add("advlist",function(a){function b(a,b){var c=[];return tinymce.each(b.split(/[ ,]/),function(a){c.push({text:a.replace(/\-/g," ").replace(/\b\w/g,function(a){return a.toUpperCase()}),data:"default"==a?"":a})}),c}function c(b,c){a.undoManager.transact(function(){var d,e=a.dom,f=a.selection;if(d=e.getParent(f.getNode(),"ol,ul"),!d||d.nodeName!=b||c===!1){var h={"list-style-type":c?c:""};a.execCommand("UL"==b?"InsertUnorderedList":"InsertOrderedList",!1,h)}c=c===!1?g[b]:c,g[b]=c,d=e.getParent(f.getNode(),"ol,ul"),d&&(e.setStyle(d,"listStyleType",c?c:null),d.removeAttribute("data-mce-style")),a.focus()})}function d(b){var c=a.dom.getStyle(a.dom.getParent(a.selection.getNode(),"ol,ul"),"listStyleType")||"";b.control.items().each(function(a){a.active(a.settings.data===c)})}var e,f,g={};e=b("OL",a.getParam("advlist_number_styles","default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman")),f=b("UL",a.getParam("advlist_bullet_styles","default,circle,disc,square")),a.addButton("numlist",{type:"splitbutton",tooltip:"Numbered list",menu:e,onshow:d,onselect:function(a){c("OL",a.control.settings.data)},onclick:function(){c("OL",!1)}}),a.addButton("bullist",{type:"splitbutton",tooltip:"Bullet list",menu:f,onshow:d,onselect:function(a){c("UL",a.control.settings.data)},onclick:function(){c("UL",!1)}})});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
tinymce.PluginManager.add("contextmenu",function(a){var b,c=a.settings.contextmenu_never_use_native;a.on("contextmenu",function(d){var e,f=a.getDoc();if(!d.ctrlKey||c){if(d.preventDefault(),tinymce.Env.mac&&tinymce.Env.webkit&&2==d.button&&f.caretRangeFromPoint&&a.selection.setRng(f.caretRangeFromPoint(d.x,d.y)),e=a.settings.contextmenu||"link image inserttable | cell row column deletetable",b)b.show();else{var g=[];tinymce.each(e.split(/[ ,]/),function(b){var c=a.menuItems[b];"|"==b&&(c={text:b}),c&&(c.shortcut="",g.push(c))});for(var h=0;h<g.length;h++)"|"==g[h].text&&(0!==h&&h!=g.length-1||g.splice(h,1));b=new tinymce.ui.Menu({items:g,context:"contextmenu",classes:"contextmenu"}).renderTo(),a.on("remove",function(){b.remove(),b=null})}var i={x:d.pageX,y:d.pageY};a.inline||(i=tinymce.DOM.getPos(a.getContentAreaContainer()),i.x+=d.clientX,i.y+=d.clientY),b.moveTo(i.x,i.y)}})});
|
||||
tinymce.PluginManager.add("contextmenu",function(a){var b,c=a.settings.contextmenu_never_use_native,d=function(a){return a.ctrlKey&&!c},e=function(){return tinymce.Env.mac&&tinymce.Env.webkit};a.on("mousedown",function(b){e()&&2===b.button&&!d(b)&&a.selection.isCollapsed()&&a.once("contextmenu",function(b){a.selection.placeCaretAt(b.clientX,b.clientY)})}),a.on("contextmenu",function(c){var e;if(!d(c)){if(c.preventDefault(),e=a.settings.contextmenu||"link image inserttable | cell row column deletetable",b)b.show();else{var f=[];tinymce.each(e.split(/[ ,]/),function(b){var c=a.menuItems[b];"|"==b&&(c={text:b}),c&&(c.shortcut="",f.push(c))});for(var g=0;g<f.length;g++)"|"==f[g].text&&(0!==g&&g!=f.length-1||f.splice(g,1));b=new tinymce.ui.Menu({items:f,context:"contextmenu",classes:"contextmenu"}).renderTo(),a.on("remove",function(){b.remove(),b=null})}var h={x:c.pageX,y:c.pageY};a.inline||(h=tinymce.DOM.getPos(a.getContentAreaContainer()),h.x+=c.clientX,h.y+=c.clientY),b.moveTo(h.x,h.y)}})});
|
||||
@@ -1 +1 @@
|
||||
tinymce.PluginManager.add("fullscreen",function(a){function b(){var a,b,c=window,d=document,e=d.body;return e.offsetWidth&&(a=e.offsetWidth,b=e.offsetHeight),c.innerWidth&&c.innerHeight&&(a=c.innerWidth,b=c.innerHeight),{w:a,h:b}}function c(){var a=tinymce.DOM.getViewPort();return{x:a.x,y:a.y}}function d(a){scrollTo(a.x,a.y)}function e(){function e(){m.setStyle(p,"height",b().h-(o.clientHeight-p.clientHeight))}var n,o,p,q,r=document.body,s=document.documentElement;l=!l,o=a.getContainer(),n=o.style,p=a.getContentAreaContainer().firstChild,q=p.style,l?(k=c(),f=q.width,g=q.height,q.width=q.height="100%",i=n.width,j=n.height,n.width=n.height="",m.addClass(r,"mce-fullscreen"),m.addClass(s,"mce-fullscreen"),m.addClass(o,"mce-fullscreen"),m.bind(window,"resize",e),e(),h=e):(q.width=f,q.height=g,i&&(n.width=i),j&&(n.height=j),m.removeClass(r,"mce-fullscreen"),m.removeClass(s,"mce-fullscreen"),m.removeClass(o,"mce-fullscreen"),m.unbind(window,"resize",h),d(k)),a.fire("FullscreenStateChanged",{state:l})}var f,g,h,i,j,k,l=!1,m=tinymce.DOM;return a.settings.inline?void 0:(a.on("init",function(){a.addShortcut("Meta+Alt+F","",e)}),a.on("remove",function(){h&&m.unbind(window,"resize",h)}),a.addCommand("mceFullScreen",e),a.addMenuItem("fullscreen",{text:"Fullscreen",shortcut:"Meta+Alt+F",selectable:!0,onClick:function(){e(),a.focus()},onPostRender:function(){var b=this;a.on("FullscreenStateChanged",function(a){b.active(a.state)})},context:"view"}),a.addButton("fullscreen",{tooltip:"Fullscreen",shortcut:"Meta+Alt+F",onClick:e,onPostRender:function(){var b=this;a.on("FullscreenStateChanged",function(a){b.active(a.state)})}}),{isFullscreen:function(){return l}})});
|
||||
tinymce.PluginManager.add("fullscreen",function(a){function b(){var a,b,c=window,d=document,e=d.body;return e.offsetWidth&&(a=e.offsetWidth,b=e.offsetHeight),c.innerWidth&&c.innerHeight&&(a=c.innerWidth,b=c.innerHeight),{w:a,h:b}}function c(){var a=tinymce.DOM.getViewPort();return{x:a.x,y:a.y}}function d(a){scrollTo(a.x,a.y)}function e(){function e(){m.setStyle(p,"height",b().h-(o.clientHeight-p.clientHeight))}var n,o,p,q,r=document.body,s=document.documentElement;l=!l,o=a.getContainer(),n=o.style,p=a.getContentAreaContainer().firstChild,q=p.style,l?(k=c(),f=q.width,g=q.height,q.width=q.height="100%",i=n.width,j=n.height,n.width=n.height="",m.addClass(r,"mce-fullscreen"),m.addClass(s,"mce-fullscreen"),m.addClass(o,"mce-fullscreen"),m.bind(window,"resize",e),e(),h=e):(q.width=f,q.height=g,i&&(n.width=i),j&&(n.height=j),m.removeClass(r,"mce-fullscreen"),m.removeClass(s,"mce-fullscreen"),m.removeClass(o,"mce-fullscreen"),m.unbind(window,"resize",h),d(k)),a.fire("FullscreenStateChanged",{state:l})}var f,g,h,i,j,k,l=!1,m=tinymce.DOM;return a.settings.inline?void 0:(a.on("init",function(){a.addShortcut("Ctrl+Shift+F","",e)}),a.on("remove",function(){h&&m.unbind(window,"resize",h)}),a.addCommand("mceFullScreen",e),a.addMenuItem("fullscreen",{text:"Fullscreen",shortcut:"Ctrl+Shift+F",selectable:!0,onClick:function(){e(),a.focus()},onPostRender:function(){var b=this;a.on("FullscreenStateChanged",function(a){b.active(a.state)})},context:"view"}),a.addButton("fullscreen",{tooltip:"Fullscreen",shortcut:"Ctrl+Shift+F",onClick:e,onPostRender:function(){var b=this;a.on("FullscreenStateChanged",function(a){b.active(a.state)})}}),{isFullscreen:function(){return l}})});
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
tinymce.PluginManager.add("importcss",function(a){function b(a){var b=tinymce.Env.cacheSuffix;return"string"==typeof a&&(a=a.replace("?"+b,"").replace("&"+b,"")),a}function c(b){var c=a.settings,d=c.skin!==!1?c.skin||"lightgray":!1;if(d){var e=c.skin_url;return e=e?a.documentBaseURI.toAbsolute(e):tinymce.baseURL+"/skins/"+d,b===e+"/content"+(a.inline?".inline":"")+".min.css"}return!1}function d(a){return"string"==typeof a?function(b){return-1!==b.indexOf(a)}:a instanceof RegExp?function(b){return a.test(b)}:a}function e(d,e){function f(a,d){var i,j=a.href;if(j=b(j),j&&e(j,d)&&!c(j)){h(a.imports,function(a){f(a,!0)});try{i=a.cssRules||a.rules}catch(k){}h(i,function(a){a.styleSheet?f(a.styleSheet,!0):a.selectorText&&h(a.selectorText.split(","),function(a){g.push(tinymce.trim(a))})})}}var g=[],i={};h(a.contentCSS,function(a){i[a]=!0}),e||(e=function(a,b){return b||i[a]});try{h(d.styleSheets,function(a){f(a)})}catch(j){}return g}function f(b){var c,d=/^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(b);if(d){var e=d[1],f=d[2].substr(1).split(".").join(" "),g=tinymce.makeMap("a,img");return d[1]?(c={title:b},a.schema.getTextBlockElements()[e]?c.block=e:a.schema.getBlockElements()[e]||g[e.toLowerCase()]?c.selector=e:c.inline=e):d[2]&&(c={inline:"span",title:b.substr(1),classes:f}),a.settings.importcss_merge_classes!==!1?c.classes=f:c.attributes={"class":f},c}}var g=this,h=tinymce.each;a.on("renderFormatsMenu",function(b){var c=a.settings,i={},j=c.importcss_selector_converter||f,k=d(c.importcss_selector_filter),l=b.control;a.settings.importcss_append||l.items().remove();var m=[];tinymce.each(c.importcss_groups,function(a){a=tinymce.extend({},a),a.filter=d(a.filter),m.push(a)}),h(e(b.doc||a.getDoc(),d(c.importcss_file_filter)),function(b){if(-1===b.indexOf(".mce-")&&!i[b]&&(!k||k(b))){var c,d=j.call(g,b);if(d){var e=d.name||tinymce.DOM.uniqueId();if(m)for(var f=0;f<m.length;f++)if(!m[f].filter||m[f].filter(b)){m[f].item||(m[f].item={text:m[f].title,menu:[]}),c=m[f].item.menu;break}a.formatter.register(e,d);var h=tinymce.extend({},l.settings.itemDefaults,{text:d.title,format:e});c?c.push(h):l.add(h)}i[b]=!0}}),h(m,function(a){l.add(a.item)}),b.control.renderNew()}),g.convertSelectorToFormat=f});
|
||||
tinymce.PluginManager.add("importcss",function(a){function b(a){var b=tinymce.Env.cacheSuffix;return"string"==typeof a&&(a=a.replace("?"+b,"").replace("&"+b,"")),a}function c(b){var c=a.settings,d=c.skin!==!1?c.skin||"lightgray":!1;if(d){var e=c.skin_url;return e=e?a.documentBaseURI.toAbsolute(e):tinymce.baseURL+"/skins/"+d,b===e+"/content"+(a.inline?".inline":"")+".min.css"}return!1}function d(a){return"string"==typeof a?function(b){return-1!==b.indexOf(a)}:a instanceof RegExp?function(b){return a.test(b)}:a}function e(d,e){function f(a,d){var h,i=a.href;if(i=b(i),i&&e(i,d)&&!c(i)){n(a.imports,function(a){f(a,!0)});try{h=a.cssRules||a.rules}catch(j){}n(h,function(a){a.styleSheet?f(a.styleSheet,!0):a.selectorText&&n(a.selectorText.split(","),function(a){g.push(tinymce.trim(a))})})}}var g=[],h={};n(a.contentCSS,function(a){h[a]=!0}),e||(e=function(a,b){return b||h[a]});try{n(d.styleSheets,function(a){f(a)})}catch(i){}return g}function f(b){var c,d=/^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(b);if(d){var e=d[1],f=d[2].substr(1).split(".").join(" "),g=tinymce.makeMap("a,img");return d[1]?(c={title:b},a.schema.getTextBlockElements()[e]?c.block=e:a.schema.getBlockElements()[e]||g[e.toLowerCase()]?c.selector=e:c.inline=e):d[2]&&(c={inline:"span",title:b.substr(1),classes:f}),a.settings.importcss_merge_classes!==!1?c.classes=f:c.attributes={"class":f},c}}function g(a,b){return tinymce.util.Tools.grep(a,function(a){return!a.filter||a.filter(b)})}function h(a){return tinymce.util.Tools.map(a,function(a){return tinymce.util.Tools.extend({},a,{original:a,selectors:{},filter:d(a.filter),item:{text:a.title,menu:[]}})})}function i(a,b){return null===b||a.settings.importcss_exclusive!==!1}function j(b,c,d){return!(i(a,c)?b in d:b in c.selectors)}function k(b,c,d){i(a,c)?d[b]=!0:c.selectors[b]=!0}function l(b,c,d){var e,g=a.settings;return e=d&&d.selector_converter?d.selector_converter:g.importcss_selector_converter?g.importcss_selector_converter:f,e.call(b,c,d)}var m=this,n=tinymce.each;a.on("renderFormatsMenu",function(b){var c=a.settings,f={},i=d(c.importcss_selector_filter),o=b.control,p=h(c.importcss_groups),q=function(b,c){if(j(b,c,f)){k(b,c,f);var d=l(m,b,c);if(d){var e=d.name||tinymce.DOM.uniqueId();return a.formatter.register(e,d),tinymce.extend({},o.settings.itemDefaults,{text:d.title,format:e})}}return null};a.settings.importcss_append||o.items().remove(),n(e(b.doc||a.getDoc(),d(c.importcss_file_filter)),function(a){if(-1===a.indexOf(".mce-")&&(!i||i(a))){var b=g(p,a);if(b.length>0)tinymce.util.Tools.each(b,function(b){var c=q(a,b);c&&b.item.menu.push(c)});else{var c=q(a,null);c&&o.add(c)}}}),n(p,function(a){a.item.menu.length>0&&o.add(a.item)}),b.control.renderNew()}),m.convertSelectorToFormat=f});
|
||||
@@ -1 +1 @@
|
||||
!function(a){a.on("AddEditor",function(a){a.editor.settings.inline_styles=!1}),a.PluginManager.add("legacyoutput",function(b,c,d){b.on("init",function(){var c="p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img",d=a.explode(b.settings.font_size_style_values),e=b.schema;b.formatter.register({alignleft:{selector:c,attributes:{align:"left"}},aligncenter:{selector:c,attributes:{align:"center"}},alignright:{selector:c,attributes:{align:"right"}},alignjustify:{selector:c,attributes:{align:"justify"}},bold:[{inline:"b",remove:"all"},{inline:"strong",remove:"all"},{inline:"span",styles:{fontWeight:"bold"}}],italic:[{inline:"i",remove:"all"},{inline:"em",remove:"all"},{inline:"span",styles:{fontStyle:"italic"}}],underline:[{inline:"u",remove:"all"},{inline:"span",styles:{textDecoration:"underline"},exact:!0}],strikethrough:[{inline:"strike",remove:"all"},{inline:"span",styles:{textDecoration:"line-through"},exact:!0}],fontname:{inline:"font",attributes:{face:"%value"}},fontsize:{inline:"font",attributes:{size:function(b){return a.inArray(d,b.value)+1}}},forecolor:{inline:"font",attributes:{color:"%value"}},hilitecolor:{inline:"font",styles:{backgroundColor:"%value"}}}),a.each("b,i,u,strike".split(","),function(a){e.addValidElements(a+"[*]")}),e.getElementRule("font")||e.addValidElements("font[face|size|color|style]"),a.each(c.split(","),function(a){var b=e.getElementRule(a);b&&(b.attributes.align||(b.attributes.align={},b.attributesOrder.push("align")))})}),b.addButton("fontsizeselect",function(){var a=[],c="8pt=1 10pt=2 12pt=3 14pt=4 18pt=5 24pt=6 36pt=7",d=b.settings.fontsize_formats||c;return b.$.each(d.split(" "),function(b,c){var d=c,e=c,f=c.split("=");f.length>1&&(d=f[0],e=f[1]),a.push({text:d,value:e})}),{type:"listbox",text:"Font Sizes",tooltip:"Font Sizes",values:a,fixedWidth:!0,onPostRender:function(){var a=this;b.on("NodeChange",function(){var c;c=b.dom.getParent(b.selection.getNode(),"font"),c?a.value(c.size):a.value("")})},onclick:function(a){a.control.settings.value&&b.execCommand("FontSize",!1,a.control.settings.value)}}}),b.addButton("fontselect",function(){function a(a){a=a.replace(/;$/,"").split(";");for(var b=a.length;b--;)a[b]=a[b].split("=");return a}var c="Andale Mono=andale mono,monospace;Arial=arial,helvetica,sans-serif;Arial Black=arial black,sans-serif;Book Antiqua=book antiqua,palatino,serif;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier,monospace;Georgia=georgia,palatino,serif;Helvetica=helvetica,arial,sans-serif;Impact=impact,sans-serif;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco,monospace;Times New Roman=times new roman,times,serif;Trebuchet MS=trebuchet ms,geneva,sans-serif;Verdana=verdana,geneva,sans-serif;Webdings=webdings;Wingdings=wingdings,zapf dingbats",e=[],f=a(b.settings.font_formats||c);return d.each(f,function(a,b){e.push({text:{raw:b[0]},value:b[1],textStyle:-1==b[1].indexOf("dings")?"font-family:"+b[1]:""})}),{type:"listbox",text:"Font Family",tooltip:"Font Family",values:e,fixedWidth:!0,onPostRender:function(){var a=this;b.on("NodeChange",function(){var c;c=b.dom.getParent(b.selection.getNode(),"font"),c?a.value(c.face):a.value("")})},onselect:function(a){a.control.settings.value&&b.execCommand("FontName",!1,a.control.settings.value)}}})})}(tinymce);
|
||||
!function(a){a.PluginManager.add("legacyoutput",function(b,c,d){b.settings.inline_styles=!1,b.on("init",function(){var c="p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img",d=a.explode(b.settings.font_size_style_values),e=b.schema;b.formatter.register({alignleft:{selector:c,attributes:{align:"left"}},aligncenter:{selector:c,attributes:{align:"center"}},alignright:{selector:c,attributes:{align:"right"}},alignjustify:{selector:c,attributes:{align:"justify"}},bold:[{inline:"b",remove:"all"},{inline:"strong",remove:"all"},{inline:"span",styles:{fontWeight:"bold"}}],italic:[{inline:"i",remove:"all"},{inline:"em",remove:"all"},{inline:"span",styles:{fontStyle:"italic"}}],underline:[{inline:"u",remove:"all"},{inline:"span",styles:{textDecoration:"underline"},exact:!0}],strikethrough:[{inline:"strike",remove:"all"},{inline:"span",styles:{textDecoration:"line-through"},exact:!0}],fontname:{inline:"font",attributes:{face:"%value"}},fontsize:{inline:"font",attributes:{size:function(b){return a.inArray(d,b.value)+1}}},forecolor:{inline:"font",attributes:{color:"%value"}},hilitecolor:{inline:"font",styles:{backgroundColor:"%value"}}}),a.each("b,i,u,strike".split(","),function(a){e.addValidElements(a+"[*]")}),e.getElementRule("font")||e.addValidElements("font[face|size|color|style]"),a.each(c.split(","),function(a){var b=e.getElementRule(a);b&&(b.attributes.align||(b.attributes.align={},b.attributesOrder.push("align")))})}),b.addButton("fontsizeselect",function(){var a=[],c="8pt=1 10pt=2 12pt=3 14pt=4 18pt=5 24pt=6 36pt=7",d=b.settings.fontsize_formats||c;return b.$.each(d.split(" "),function(b,c){var d=c,e=c,f=c.split("=");f.length>1&&(d=f[0],e=f[1]),a.push({text:d,value:e})}),{type:"listbox",text:"Font Sizes",tooltip:"Font Sizes",values:a,fixedWidth:!0,onPostRender:function(){var a=this;b.on("NodeChange",function(){var c;c=b.dom.getParent(b.selection.getNode(),"font"),c?a.value(c.size):a.value("")})},onclick:function(a){a.control.settings.value&&b.execCommand("FontSize",!1,a.control.settings.value)}}}),b.addButton("fontselect",function(){function a(a){a=a.replace(/;$/,"").split(";");for(var b=a.length;b--;)a[b]=a[b].split("=");return a}var c="Andale Mono=andale mono,monospace;Arial=arial,helvetica,sans-serif;Arial Black=arial black,sans-serif;Book Antiqua=book antiqua,palatino,serif;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier,monospace;Georgia=georgia,palatino,serif;Helvetica=helvetica,arial,sans-serif;Impact=impact,sans-serif;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco,monospace;Times New Roman=times new roman,times,serif;Trebuchet MS=trebuchet ms,geneva,sans-serif;Verdana=verdana,geneva,sans-serif;Webdings=webdings;Wingdings=wingdings,zapf dingbats",e=[],f=a(b.settings.font_formats||c);return d.each(f,function(a,b){e.push({text:{raw:b[0]},value:b[1],textStyle:-1==b[1].indexOf("dings")?"font-family:"+b[1]:""})}),{type:"listbox",text:"Font Family",tooltip:"Font Family",values:e,fixedWidth:!0,onPostRender:function(){var a=this;b.on("NodeChange",function(){var c;c=b.dom.getParent(b.selection.getNode(),"font"),c?a.value(c.face):a.value("")})},onselect:function(a){a.control.settings.value&&b.execCommand("FontName",!1,a.control.settings.value)}}})})}(tinymce);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
tinymce.PluginManager.add("preview",function(a){var b=a.settings,c=!tinymce.Env.ie;a.addCommand("mcePreview",function(){a.windowManager.open({title:"Preview",width:parseInt(a.getParam("plugin_preview_width","650"),10),height:parseInt(a.getParam("plugin_preview_height","500"),10),html:'<iframe src="javascript:\'\'" frameborder="0"'+(c?' sandbox="allow-scripts"':"")+"></iframe>",buttons:{text:"Close",onclick:function(){this.parent().parent().close()}},onPostRender:function(){var d,e="";e+='<base href="'+a.documentBaseURI.getURI()+'">',tinymce.each(a.contentCSS,function(b){e+='<link type="text/css" rel="stylesheet" href="'+a.documentBaseURI.toAbsolute(b)+'">'});var f=b.body_id||"tinymce";-1!=f.indexOf("=")&&(f=a.getParam("body_id","","hash"),f=f[a.id]||f);var g=b.body_class||"";-1!=g.indexOf("=")&&(g=a.getParam("body_class","","hash"),g=g[a.id]||"");var h=a.settings.directionality?' dir="'+a.settings.directionality+'"':"";if(d="<!DOCTYPE html><html><head>"+e+'</head><body id="'+f+'" class="mce-content-body '+g+'"'+h+">"+a.getContent()+"</body></html>",c)this.getEl("body").firstChild.src="data:text/html;charset=utf-8,"+encodeURIComponent(d);else{var i=this.getEl("body").firstChild.contentWindow.document;i.open(),i.write(d),i.close()}}})}),a.addButton("preview",{title:"Preview",cmd:"mcePreview"}),a.addMenuItem("preview",{text:"Preview",cmd:"mcePreview",context:"view"})});
|
||||
tinymce.PluginManager.add("preview",function(a){var b=a.settings,c=!tinymce.Env.ie;a.addCommand("mcePreview",function(){a.windowManager.open({title:"Preview",width:parseInt(a.getParam("plugin_preview_width","650"),10),height:parseInt(a.getParam("plugin_preview_height","500"),10),html:'<iframe src="javascript:\'\'" frameborder="0"'+(c?' sandbox="allow-scripts"':"")+"></iframe>",buttons:{text:"Close",onclick:function(){this.parent().parent().close()}},onPostRender:function(){var d,e="";e+='<base href="'+a.documentBaseURI.getURI()+'">',tinymce.each(a.contentCSS,function(b){e+='<link type="text/css" rel="stylesheet" href="'+a.documentBaseURI.toAbsolute(b)+'">'});var f=b.body_id||"tinymce";-1!=f.indexOf("=")&&(f=a.getParam("body_id","","hash"),f=f[a.id]||f);var g=b.body_class||"";-1!=g.indexOf("=")&&(g=a.getParam("body_class","","hash"),g=g[a.id]||"");var h='<script>document.addEventListener && document.addEventListener("click", function(e) {for (var elm = e.target; elm; elm = elm.parentNode) {if (elm.nodeName === "A") {e.preventDefault();}}}, false);</script> ',i=a.settings.directionality?' dir="'+a.settings.directionality+'"':"";if(d="<!DOCTYPE html><html><head>"+e+'</head><body id="'+f+'" class="mce-content-body '+g+'"'+i+">"+a.getContent()+h+"</body></html>",c)this.getEl("body").firstChild.src="data:text/html;charset=utf-8,"+encodeURIComponent(d);else{var j=this.getEl("body").firstChild.contentWindow.document;j.open(),j.write(d),j.close()}}})}),a.addButton("preview",{title:"Preview",cmd:"mcePreview"}),a.addMenuItem("preview",{text:"Preview",cmd:"mcePreview",context:"view"})});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
tinymce.PluginManager.add("template",function(a){function b(b){return function(){var c=a.settings.templates;return"function"==typeof c?void c(b):void("string"==typeof c?tinymce.util.XHR.send({url:c,success:function(a){b(tinymce.util.JSON.parse(a))}}):b(c))}}function c(b){function c(b){function c(b){if(-1==b.indexOf("<html>")){var c="";tinymce.each(a.contentCSS,function(b){c+='<link type="text/css" rel="stylesheet" href="'+a.documentBaseURI.toAbsolute(b)+'">'}),b="<!DOCTYPE html><html><head>"+c+"</head><body>"+b+"</body></html>"}b=f(b,"template_preview_replace_values");var e=d.find("iframe")[0].getEl().contentWindow.document;e.open(),e.write(b),e.close()}var g=b.control.value();g.url?tinymce.util.XHR.send({url:g.url,success:function(a){e=a,c(e)}}):(e=g.content,c(e)),d.find("#description")[0].text(b.control.value().description)}var d,e,h=[];if(!b||0===b.length){var i=a.translate("No templates defined.");return void a.notificationManager.open({text:i,type:"info"})}tinymce.each(b,function(a){h.push({selected:!h.length,text:a.title,value:{url:a.url,content:a.content,description:a.description}})}),d=a.windowManager.open({title:"Insert template",layout:"flex",direction:"column",align:"stretch",padding:15,spacing:10,items:[{type:"form",flex:0,padding:0,items:[{type:"container",label:"Templates",items:{type:"listbox",label:"Templates",name:"template",values:h,onselect:c}}]},{type:"label",name:"description",label:"Description",text:"\xa0"},{type:"iframe",flex:1,border:1}],onsubmit:function(){g(!1,e)},width:a.getParam("template_popup_width",600),height:a.getParam("template_popup_height",500)}),d.find("listbox")[0].fire("select")}function d(b,c){function d(a,b){if(a=""+a,a.length<b)for(var c=0;c<b-a.length;c++)a="0"+a;return a}var e="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),f="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),g="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),h="January February March April May June July August September October November December".split(" ");return c=c||new Date,b=b.replace("%D","%m/%d/%Y"),b=b.replace("%r","%I:%M:%S %p"),b=b.replace("%Y",""+c.getFullYear()),b=b.replace("%y",""+c.getYear()),b=b.replace("%m",d(c.getMonth()+1,2)),b=b.replace("%d",d(c.getDate(),2)),b=b.replace("%H",""+d(c.getHours(),2)),b=b.replace("%M",""+d(c.getMinutes(),2)),b=b.replace("%S",""+d(c.getSeconds(),2)),b=b.replace("%I",""+((c.getHours()+11)%12+1)),b=b.replace("%p",""+(c.getHours()<12?"AM":"PM")),b=b.replace("%B",""+a.translate(h[c.getMonth()])),b=b.replace("%b",""+a.translate(g[c.getMonth()])),b=b.replace("%A",""+a.translate(f[c.getDay()])),b=b.replace("%a",""+a.translate(e[c.getDay()])),b=b.replace("%%","%")}function e(b){var c=a.dom,d=a.getParam("template_replace_values");h(c.select("*",b),function(a){h(d,function(b,e){c.hasClass(a,e)&&"function"==typeof d[e]&&d[e](a)})})}function f(b,c){return h(a.getParam(c),function(a,c){"function"==typeof a&&(a=a(c)),b=b.replace(new RegExp("\\{\\$"+c+"\\}","g"),a)}),b}function g(b,c){function g(a,b){return new RegExp("\\b"+b+"\\b","g").test(a.className)}var i,j,k=a.dom,l=a.selection.getContent();c=f(c,"template_replace_values"),i=k.create("div",null,c),j=k.select(".mceTmpl",i),j&&j.length>0&&(i=k.create("div",null),i.appendChild(j[0].cloneNode(!0))),h(k.select("*",i),function(b){g(b,a.getParam("template_cdate_classes","cdate").replace(/\s+/g,"|"))&&(b.innerHTML=d(a.getParam("template_cdate_format",a.getLang("template.cdate_format")))),g(b,a.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(b.innerHTML=d(a.getParam("template_mdate_format",a.getLang("template.mdate_format")))),g(b,a.getParam("template_selected_content_classes","selcontent").replace(/\s+/g,"|"))&&(b.innerHTML=l)}),e(i),a.execCommand("mceInsertContent",!1,i.innerHTML),a.addVisual()}var h=tinymce.each;a.addCommand("mceInsertTemplate",g),a.addButton("template",{title:"Insert template",onclick:b(c)}),a.addMenuItem("template",{text:"Insert template",onclick:b(c),context:"insert"}),a.on("PreProcess",function(b){var c=a.dom;h(c.select("div",b.node),function(b){c.hasClass(b,"mceTmpl")&&(h(c.select("*",b),function(b){c.hasClass(b,a.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(b.innerHTML=d(a.getParam("template_mdate_format",a.getLang("template.mdate_format"))))}),e(b))})})});
|
||||
tinymce.PluginManager.add("template",function(a){function b(b){return function(){var c=a.settings.templates;return"function"==typeof c?void c(b):void("string"==typeof c?tinymce.util.XHR.send({url:c,success:function(a){b(tinymce.util.JSON.parse(a))}}):b(c))}}function c(b){function c(b){function c(b){if(-1==b.indexOf("<html>")){var c="";tinymce.each(a.contentCSS,function(b){c+='<link type="text/css" rel="stylesheet" href="'+a.documentBaseURI.toAbsolute(b)+'">'});var e=a.settings.body_class||"";-1!=e.indexOf("=")&&(e=a.getParam("body_class","","hash"),e=e[a.id]||""),b="<!DOCTYPE html><html><head>"+c+'</head><body class="'+e+'">'+b+"</body></html>"}b=f(b,"template_preview_replace_values");var g=d.find("iframe")[0].getEl().contentWindow.document;g.open(),g.write(b),g.close()}var g=b.control.value();g.url?tinymce.util.XHR.send({url:g.url,success:function(a){e=a,c(e)}}):(e=g.content,c(e)),d.find("#description")[0].text(b.control.value().description)}var d,e,h=[];if(!b||0===b.length){var i=a.translate("No templates defined.");return void a.notificationManager.open({text:i,type:"info"})}tinymce.each(b,function(a){h.push({selected:!h.length,text:a.title,value:{url:a.url,content:a.content,description:a.description}})}),d=a.windowManager.open({title:"Insert template",layout:"flex",direction:"column",align:"stretch",padding:15,spacing:10,items:[{type:"form",flex:0,padding:0,items:[{type:"container",label:"Templates",items:{type:"listbox",label:"Templates",name:"template",values:h,onselect:c}}]},{type:"label",name:"description",label:"Description",text:"\xa0"},{type:"iframe",flex:1,border:1}],onsubmit:function(){g(!1,e)},minWidth:Math.min(tinymce.DOM.getViewPort().w,a.getParam("template_popup_width",600)),minHeight:Math.min(tinymce.DOM.getViewPort().h,a.getParam("template_popup_height",500))}),d.find("listbox")[0].fire("select")}function d(b,c){function d(a,b){if(a=""+a,a.length<b)for(var c=0;c<b-a.length;c++)a="0"+a;return a}var e="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),f="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),g="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),h="January February March April May June July August September October November December".split(" ");return c=c||new Date,b=b.replace("%D","%m/%d/%Y"),b=b.replace("%r","%I:%M:%S %p"),b=b.replace("%Y",""+c.getFullYear()),b=b.replace("%y",""+c.getYear()),b=b.replace("%m",d(c.getMonth()+1,2)),b=b.replace("%d",d(c.getDate(),2)),b=b.replace("%H",""+d(c.getHours(),2)),b=b.replace("%M",""+d(c.getMinutes(),2)),b=b.replace("%S",""+d(c.getSeconds(),2)),b=b.replace("%I",""+((c.getHours()+11)%12+1)),b=b.replace("%p",""+(c.getHours()<12?"AM":"PM")),b=b.replace("%B",""+a.translate(h[c.getMonth()])),b=b.replace("%b",""+a.translate(g[c.getMonth()])),b=b.replace("%A",""+a.translate(f[c.getDay()])),b=b.replace("%a",""+a.translate(e[c.getDay()])),b=b.replace("%%","%")}function e(b){var c=a.dom,d=a.getParam("template_replace_values");h(c.select("*",b),function(a){h(d,function(b,e){c.hasClass(a,e)&&"function"==typeof d[e]&&d[e](a)})})}function f(b,c){return h(a.getParam(c),function(a,c){"function"==typeof a&&(a=a(c)),b=b.replace(new RegExp("\\{\\$"+c+"\\}","g"),a)}),b}function g(b,c){function g(a,b){return new RegExp("\\b"+b+"\\b","g").test(a.className)}var i,j,k=a.dom,l=a.selection.getContent();c=f(c,"template_replace_values"),i=k.create("div",null,c),j=k.select(".mceTmpl",i),j&&j.length>0&&(i=k.create("div",null),i.appendChild(j[0].cloneNode(!0))),h(k.select("*",i),function(b){g(b,a.getParam("template_cdate_classes","cdate").replace(/\s+/g,"|"))&&(b.innerHTML=d(a.getParam("template_cdate_format",a.getLang("template.cdate_format")))),g(b,a.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(b.innerHTML=d(a.getParam("template_mdate_format",a.getLang("template.mdate_format")))),g(b,a.getParam("template_selected_content_classes","selcontent").replace(/\s+/g,"|"))&&(b.innerHTML=l)}),e(i),a.execCommand("mceInsertContent",!1,i.innerHTML),a.addVisual()}var h=tinymce.each;a.addCommand("mceInsertTemplate",g),a.addButton("template",{title:"Insert template",onclick:b(c)}),a.addMenuItem("template",{text:"Insert template",onclick:b(c),context:"insert"}),a.on("PreProcess",function(b){var c=a.dom;h(c.select("div",b.node),function(b){c.hasClass(b,"mceTmpl")&&(h(c.select("*",b),function(b){c.hasClass(b,a.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(b.innerHTML=d(a.getParam("template_mdate_format",a.getLang("template.mdate_format"))))}),e(b))})})});
|
||||
@@ -1 +1 @@
|
||||
tinymce.PluginManager.add("textcolor",function(a){function b(b){var c;return a.dom.getParents(a.selection.getStart(),function(a){var d;(d=a.style["forecolor"==b?"color":"background-color"])&&(c=d)}),c}function c(){var b,c,d=[];for(c=a.settings.textcolor_map||["000000","Black","993300","Burnt orange","333300","Dark olive","003300","Dark green","003366","Dark azure","000080","Navy Blue","333399","Indigo","333333","Very dark gray","800000","Maroon","FF6600","Orange","808000","Olive","008000","Green","008080","Teal","0000FF","Blue","666699","Grayish blue","808080","Gray","FF0000","Red","FF9900","Amber","99CC00","Yellow green","339966","Sea green","33CCCC","Turquoise","3366FF","Royal blue","800080","Purple","999999","Medium gray","FF00FF","Magenta","FFCC00","Gold","FFFF00","Yellow","00FF00","Lime","00FFFF","Aqua","00CCFF","Sky blue","993366","Red violet","FFFFFF","White","FF99CC","Pink","FFCC99","Peach","FFFF99","Light yellow","CCFFCC","Pale green","CCFFFF","Pale cyan","99CCFF","Light sky blue","CC99FF","Plum"],b=0;b<c.length;b+=2)d.push({text:c[b+1],color:"#"+c[b]});return d}function d(){function b(a,b){var c="transparent"==a;return'<td class="mce-grid-cell'+(c?" mce-colorbtn-trans":"")+'"><div id="'+n+"-"+o++ +'" data-mce-color="'+(a?a:"")+'" role="option" tabIndex="-1" style="'+(a?"background-color: "+a:"")+'" title="'+tinymce.translate(b)+'">'+(c?"×":"")+"</div></td>"}var d,e,f,g,h,k,l,m=this,n=m._id,o=0;for(d=c(),d.push({text:tinymce.translate("No color"),color:"transparent"}),f='<table class="mce-grid mce-grid-border mce-colorbutton-grid" role="list" cellspacing="0"><tbody>',g=d.length-1,k=0;j>k;k++){for(f+="<tr>",h=0;i>h;h++)l=k*i+h,l>g?f+="<td></td>":(e=d[l],f+=b(e.color,e.text));f+="</tr>"}if(a.settings.color_picker_callback){for(f+='<tr><td colspan="'+i+'" class="mce-custom-color-btn"><div id="'+n+'-c" class="mce-widget mce-btn mce-btn-small mce-btn-flat" role="button" tabindex="-1" aria-labelledby="'+n+'-c" style="width: 100%"><button type="button" role="presentation" tabindex="-1">'+tinymce.translate("Custom...")+"</button></div></td></tr>",f+="<tr>",h=0;i>h;h++)f+=b("","Custom color");f+="</tr>"}return f+="</tbody></table>"}function e(b,c){a.undoManager.transact(function(){a.focus(),a.formatter.apply(b,{value:c}),a.nodeChanged()})}function f(b){a.undoManager.transact(function(){a.focus(),a.formatter.remove(b,{value:null},null,!0),a.nodeChanged()})}function g(c){function d(a){k.hidePanel(),k.color(a),e(k.settings.format,a)}function g(){k.hidePanel(),k.resetColor(),f(k.settings.format)}function h(a,b){a.style.background=b,a.setAttribute("data-mce-color",b)}var j,k=this.parent();tinymce.DOM.getParent(c.target,".mce-custom-color-btn")&&(k.hidePanel(),a.settings.color_picker_callback.call(a,function(a){var b,c,e,f=k.panel.getEl().getElementsByTagName("table")[0];for(b=tinymce.map(f.rows[f.rows.length-1].childNodes,function(a){return a.firstChild}),e=0;e<b.length&&(c=b[e],c.getAttribute("data-mce-color"));e++);if(e==i)for(e=0;i-1>e;e++)h(b[e],b[e+1].getAttribute("data-mce-color"));h(c,a),d(a)},b(k.settings.format))),j=c.target.getAttribute("data-mce-color"),j?(this.lastId&&document.getElementById(this.lastId).setAttribute("aria-selected",!1),c.target.setAttribute("aria-selected",!0),this.lastId=c.target.id,"transparent"==j?g():d(j)):null!==j&&k.hidePanel()}function h(){var a=this;a._color?e(a.settings.format,a._color):f(a.settings.format)}var i,j;j=a.settings.textcolor_rows||5,i=a.settings.textcolor_cols||8,a.addButton("forecolor",{type:"colorbutton",tooltip:"Text color",format:"forecolor",panel:{role:"application",ariaRemember:!0,html:d,onclick:g},onclick:h}),a.addButton("backcolor",{type:"colorbutton",tooltip:"Background color",format:"hilitecolor",panel:{role:"application",ariaRemember:!0,html:d,onclick:g},onclick:h})});
|
||||
tinymce.PluginManager.add("textcolor",function(a){function b(b){var c;return a.dom.getParents(a.selection.getStart(),function(a){var d;(d=a.style["forecolor"==b?"color":"background-color"])&&(c=d)}),c}function c(b){var c,d,e=[];for(d=["000000","Black","993300","Burnt orange","333300","Dark olive","003300","Dark green","003366","Dark azure","000080","Navy Blue","333399","Indigo","333333","Very dark gray","800000","Maroon","FF6600","Orange","808000","Olive","008000","Green","008080","Teal","0000FF","Blue","666699","Grayish blue","808080","Gray","FF0000","Red","FF9900","Amber","99CC00","Yellow green","339966","Sea green","33CCCC","Turquoise","3366FF","Royal blue","800080","Purple","999999","Medium gray","FF00FF","Magenta","FFCC00","Gold","FFFF00","Yellow","00FF00","Lime","00FFFF","Aqua","00CCFF","Sky blue","993366","Red violet","FFFFFF","White","FF99CC","Pink","FFCC99","Peach","FFFF99","Light yellow","CCFFCC","Pale green","CCFFFF","Pale cyan","99CCFF","Light sky blue","CC99FF","Plum"],d=a.settings.textcolor_map||d,d=a.settings[b+"_map"]||d,c=0;c<d.length;c+=2)e.push({text:d[c+1],color:"#"+d[c]});return e}function d(){function b(a,b){var c="transparent"==a;return'<td class="mce-grid-cell'+(c?" mce-colorbtn-trans":"")+'"><div id="'+o+"-"+p++ +'" data-mce-color="'+(a?a:"")+'" role="option" tabIndex="-1" style="'+(a?"background-color: "+a:"")+'" title="'+tinymce.translate(b)+'">'+(c?"×":"")+"</div></td>"}var d,e,f,g,h,k,l,m,n=this,o=n._id,p=0;for(m=n.settings.origin,d=c(m),d.push({text:tinymce.translate("No color"),color:"transparent"}),f='<table class="mce-grid mce-grid-border mce-colorbutton-grid" role="list" cellspacing="0"><tbody>',g=d.length-1,k=0;k<j[m];k++){for(f+="<tr>",h=0;h<i[m];h++)l=k*i[m]+h,l>g?f+="<td></td>":(e=d[l],f+=b(e.color,e.text));f+="</tr>"}if(a.settings.color_picker_callback){for(f+='<tr><td colspan="'+i[m]+'" class="mce-custom-color-btn"><div id="'+o+'-c" class="mce-widget mce-btn mce-btn-small mce-btn-flat" role="button" tabindex="-1" aria-labelledby="'+o+'-c" style="width: 100%"><button type="button" role="presentation" tabindex="-1">'+tinymce.translate("Custom...")+"</button></div></td></tr>",f+="<tr>",h=0;h<i[m];h++)f+=b("","Custom color");f+="</tr>"}return f+="</tbody></table>"}function e(b,c){a.undoManager.transact(function(){a.focus(),a.formatter.apply(b,{value:c}),a.nodeChanged()})}function f(b){a.undoManager.transact(function(){a.focus(),a.formatter.remove(b,{value:null},null,!0),a.nodeChanged()})}function g(c){function d(a){l.hidePanel(),l.color(a),e(l.settings.format,a)}function g(){l.hidePanel(),l.resetColor(),f(l.settings.format)}function h(a,b){a.style.background=b,a.setAttribute("data-mce-color",b)}var j,k,l=this.parent();k=l.settings.origin,tinymce.DOM.getParent(c.target,".mce-custom-color-btn")&&(l.hidePanel(),a.settings.color_picker_callback.call(a,function(a){var b,c,e,f=l.panel.getEl().getElementsByTagName("table")[0];for(b=tinymce.map(f.rows[f.rows.length-1].childNodes,function(a){return a.firstChild}),e=0;e<b.length&&(c=b[e],c.getAttribute("data-mce-color"));e++);if(e==i[k])for(e=0;e<i[k]-1;e++)h(b[e],b[e+1].getAttribute("data-mce-color"));h(c,a),d(a)},b(l.settings.format))),j=c.target.getAttribute("data-mce-color"),j?(this.lastId&&document.getElementById(this.lastId).setAttribute("aria-selected",!1),c.target.setAttribute("aria-selected",!0),this.lastId=c.target.id,"transparent"==j?g():d(j)):null!==j&&l.hidePanel()}function h(){var a=this;a._color?e(a.settings.format,a._color):f(a.settings.format)}var i,j;j={forecolor:a.settings.forecolor_rows||a.settings.textcolor_rows||5,backcolor:a.settings.backcolor_rows||a.settings.textcolor_rows||5},i={forecolor:a.settings.forecolor_cols||a.settings.textcolor_cols||8,backcolor:a.settings.backcolor_cols||a.settings.textcolor_cols||8},a.addButton("forecolor",{type:"colorbutton",tooltip:"Text color",format:"forecolor",panel:{origin:"forecolor",role:"application",ariaRemember:!0,html:d,onclick:g},onclick:h}),a.addButton("backcolor",{type:"colorbutton",tooltip:"Background color",format:"hilitecolor",panel:{origin:"backcolor",role:"application",ariaRemember:!0,html:d,onclick:g},onclick:h})});
|
||||
@@ -36,7 +36,7 @@
|
||||
<glyph unicode="" glyph-name="forecolor" d="M651.168 676.166c-24.612 81.962-28.876 91.834-107.168 91.834h-64c-79.618 0-82.664-10.152-108.418-96 0-0.002 0-0.002-0.002-0.004l-143.998-479.996h113.636l57.6 192h226.366l57.6-192h113.63l-145.246 484.166zM437.218 512l38.4 136c10.086 33.618 36.38 30 36.38 30s26.294 3.618 36.38-30h0.004l38.4-136h-149.564z" />
|
||||
<glyph unicode="" glyph-name="table" d="M64 768v-704h896v704h-896zM384 320v128h256v-128h-256zM640 256v-128h-256v128h256zM640 640v-128h-256v128h256zM320 640v-128h-192v128h192zM128 448h192v-128h-192v128zM704 448h192v-128h-192v128zM704 512v128h192v-128h-192zM128 256h192v-128h-192v128zM704 128v128h192v-128h-192z" />
|
||||
<glyph unicode="" glyph-name="hr" d="M64 512h896v-128h-896z" />
|
||||
<glyph unicode="" glyph-name="removefromat" d="M64 192h512v-128h-512v128zM768 768h-220.558l-183.766-512h-132.288l183.762 512h-223.15v128h576v-128zM929.774 64l-129.774 129.774-129.774-129.774-62.226 62.226 129.774 129.774-129.774 129.774 62.226 62.226 129.774-129.774 129.774 129.774 62.226-62.226-129.774-129.774 129.774-129.774-62.226-62.226z" />
|
||||
<glyph unicode="" glyph-name="removeformat" d="M64 192h512v-128h-512v128zM768 768h-220.558l-183.766-512h-132.288l183.762 512h-223.15v128h576v-128zM929.774 64l-129.774 129.774-129.774-129.774-62.226 62.226 129.774 129.774-129.774 129.774 62.226 62.226 129.774-129.774 129.774 129.774 62.226-62.226-129.774-129.774 129.774-129.774-62.226-62.226z" />
|
||||
<glyph unicode="" glyph-name="subscript" d="M768 50v-50h128v-64h-192v146l128 60v50h-128v64h192v-146zM676 704h-136l-188-188-188 188h-136l256-256-256-256h136l188 188 188-188h136l-256 256z" />
|
||||
<glyph unicode="" glyph-name="superscript" d="M768 754v-50h128v-64h-192v146l128 60v50h-128v64h192v-146zM676 704h-136l-188-188-188 188h-136l256-256-256-256h136l188 188 188-188h136l-256 256z" />
|
||||
<glyph unicode="" glyph-name="charmap" d="M704 128v37.004c151.348 61.628 256 193.82 256 346.996 0 212.078-200.576 384-448 384s-448-171.922-448-384c0-153.176 104.654-285.368 256-346.996v-37.004h-192l-64 96v-224h320v222.812c-100.9 51.362-170.666 161.54-170.666 289.188 0 176.732 133.718 320 298.666 320s298.666-143.268 298.666-320c0-127.648-69.766-237.826-170.666-289.188v-222.812h320v224l-64-96h-192z" />
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
27
public/libs/tinymce/tinymce.min.js
vendored
27
public/libs/tinymce/tinymce.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -69,7 +69,7 @@ module.exports = function (ngApp, events) {
|
||||
*/
|
||||
function callbackAndHide(returnData) {
|
||||
if (callback) callback(returnData);
|
||||
$scope.showing = false;
|
||||
$scope.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,6 +109,7 @@ module.exports = function (ngApp, events) {
|
||||
function show(doneCallback) {
|
||||
callback = doneCallback;
|
||||
$scope.showing = true;
|
||||
$('#image-manager').find('.overlay').css('display', 'flex').hide().fadeIn(240);
|
||||
// Get initial images if they have not yet been loaded in.
|
||||
if (!dataLoaded) {
|
||||
fetchData();
|
||||
@@ -131,6 +132,7 @@ module.exports = function (ngApp, events) {
|
||||
*/
|
||||
$scope.hide = function () {
|
||||
$scope.showing = false;
|
||||
$('#image-manager').find('.overlay').fadeOut(240);
|
||||
};
|
||||
|
||||
var baseUrl = window.baseUrl('/images/' + $scope.imageType + '/all/');
|
||||
@@ -357,8 +359,6 @@ module.exports = function (ngApp, events) {
|
||||
|
||||
/**
|
||||
* Save a draft update into the system via an AJAX request.
|
||||
* @param title
|
||||
* @param html
|
||||
*/
|
||||
function saveDraft() {
|
||||
var data = {
|
||||
@@ -373,9 +373,17 @@ module.exports = function (ngApp, events) {
|
||||
var updateTime = moment.utc(moment.unix(responseData.data.timestamp)).toDate();
|
||||
$scope.draftText = responseData.data.message + moment(updateTime).format('HH:mm');
|
||||
if (!$scope.isNewPageDraft) $scope.isUpdateDraft = true;
|
||||
showDraftSaveNotification();
|
||||
});
|
||||
}
|
||||
|
||||
function showDraftSaveNotification() {
|
||||
$scope.draftUpdated = true;
|
||||
$timeout(() => {
|
||||
$scope.draftUpdated = false;
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
$scope.forceDraftSave = function() {
|
||||
saveDraft();
|
||||
};
|
||||
|
||||
@@ -158,9 +158,22 @@ module.exports = function (ngApp, events) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs) {
|
||||
var menu = element.find('ul');
|
||||
const menu = element.find('ul');
|
||||
element.find('[dropdown-toggle]').on('click', function () {
|
||||
menu.show().addClass('anim menuIn');
|
||||
let inputs = menu.find('input');
|
||||
let hasInput = inputs.length > 0;
|
||||
if (hasInput) {
|
||||
inputs.first().focus();
|
||||
element.on('keypress', 'input', event => {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
menu.hide();
|
||||
menu.removeClass('anim menuIn');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
element.mouseleave(function () {
|
||||
menu.hide();
|
||||
menu.removeClass('anim menuIn');
|
||||
@@ -258,8 +271,6 @@ module.exports = function (ngApp, events) {
|
||||
scope.mdModel = content;
|
||||
scope.mdChange(markdown(content));
|
||||
|
||||
console.log('test');
|
||||
|
||||
element.on('change input', (event) => {
|
||||
content = element.val();
|
||||
$timeout(() => {
|
||||
@@ -291,6 +302,7 @@ module.exports = function (ngApp, events) {
|
||||
const input = element.find('[markdown-input] textarea').first();
|
||||
const display = element.find('.markdown-display').first();
|
||||
const insertImage = element.find('button[data-action="insertImage"]');
|
||||
const insertEntityLink = element.find('button[data-action="insertEntityLink"]')
|
||||
|
||||
let currentCaretPos = 0;
|
||||
|
||||
@@ -342,6 +354,13 @@ module.exports = function (ngApp, events) {
|
||||
input[0].selectionEnd = caretPos + (';
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert entity link shortcut
|
||||
if (event.which === 75 && event.ctrlKey && event.shiftKey) {
|
||||
showLinkSelector();
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass key presses to controller via event
|
||||
scope.$emit('editor-keydown', event);
|
||||
});
|
||||
@@ -351,12 +370,109 @@ module.exports = function (ngApp, events) {
|
||||
window.ImageManager.showExternal(image => {
|
||||
let caretPos = currentCaretPos;
|
||||
let currentContent = input.val();
|
||||
let mdImageText = "";
|
||||
let mdImageText = "";
|
||||
input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
|
||||
input.change();
|
||||
});
|
||||
});
|
||||
|
||||
function showLinkSelector() {
|
||||
window.showEntityLinkSelector((entity) => {
|
||||
let selectionStart = currentCaretPos;
|
||||
let selectionEnd = input[0].selectionEnd;
|
||||
let textSelected = (selectionEnd !== selectionStart);
|
||||
let currentContent = input.val();
|
||||
|
||||
if (textSelected) {
|
||||
let selectedText = currentContent.substring(selectionStart, selectionEnd);
|
||||
let linkText = `[${selectedText}](${entity.link})`;
|
||||
input.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionEnd));
|
||||
} else {
|
||||
let linkText = ` [${entity.name}](${entity.link}) `;
|
||||
input.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionStart))
|
||||
}
|
||||
input.change();
|
||||
});
|
||||
}
|
||||
insertEntityLink.click(showLinkSelector);
|
||||
|
||||
// Upload and insert image on paste
|
||||
function editorPaste(e) {
|
||||
e = e.originalEvent;
|
||||
if (!e.clipboardData) return
|
||||
var items = e.clipboardData.items;
|
||||
if (!items) return;
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
uploadImage(items[i].getAsFile());
|
||||
}
|
||||
}
|
||||
|
||||
input.on('paste', editorPaste);
|
||||
|
||||
// Handle image drop, Uploads images to BookStack.
|
||||
function handleImageDrop(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let files = event.originalEvent.dataTransfer.files;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
uploadImage(files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
input.on('drop', handleImageDrop);
|
||||
|
||||
// Handle image upload and add image into markdown content
|
||||
function uploadImage(file) {
|
||||
if (file.type.indexOf('image') !== 0) return;
|
||||
var formData = new FormData();
|
||||
var ext = 'png';
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
if (file.name) {
|
||||
var fileNameMatches = file.name.match(/\.(.+)$/);
|
||||
if (fileNameMatches) {
|
||||
ext = fileNameMatches[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Insert image into markdown
|
||||
let id = "image-" + Math.random().toString(16).slice(2);
|
||||
let selectStart = input[0].selectionStart;
|
||||
let selectEnd = input[0].selectionEnd;
|
||||
let content = input[0].value;
|
||||
let selectText = content.substring(selectStart, selectEnd);
|
||||
let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
|
||||
let innerContent = ((selectEnd > selectStart) ? `![${selectText}]` : '![]') + `(${placeholderImage})`;
|
||||
input[0].value = content.substring(0, selectStart) + innerContent + content.substring(selectEnd);
|
||||
|
||||
input.focus();
|
||||
input[0].selectionStart = selectStart;
|
||||
input[0].selectionEnd = selectStart;
|
||||
|
||||
let remoteFilename = "image-" + Date.now() + "." + ext;
|
||||
formData.append('file', file, remoteFilename);
|
||||
formData.append('_token', document.querySelector('meta[name="token"]').getAttribute('content'));
|
||||
|
||||
xhr.open('POST', window.baseUrl('/images/gallery/upload'));
|
||||
xhr.onload = function () {
|
||||
let selectStart = input[0].selectionStart;
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
var result = JSON.parse(xhr.responseText);
|
||||
input[0].value = input[0].value.replace(placeholderImage, result.thumbs.display);
|
||||
input.change();
|
||||
} else {
|
||||
console.log('An error occurred uploading the image');
|
||||
console.log(xhr.responseText);
|
||||
input[0].value = input[0].value.replace(innerContent, '');
|
||||
input.change();
|
||||
}
|
||||
input.focus();
|
||||
input[0].selectionStart = selectStart;
|
||||
input[0].selectionEnd = selectStart;
|
||||
};
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}]);
|
||||
@@ -587,6 +703,58 @@ module.exports = function (ngApp, events) {
|
||||
}
|
||||
}]);
|
||||
|
||||
ngApp.directive('entityLinkSelector', [function($http) {
|
||||
return {
|
||||
restict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
const selectButton = element.find('.entity-link-selector-confirm');
|
||||
let callback = false;
|
||||
let entitySelection = null;
|
||||
|
||||
// Handle entity selection change, Stores the selected entity locally
|
||||
function entitySelectionChange(entity) {
|
||||
entitySelection = entity;
|
||||
if (entity === null) {
|
||||
selectButton.attr('disabled', 'true');
|
||||
} else {
|
||||
selectButton.removeAttr('disabled');
|
||||
}
|
||||
}
|
||||
events.listen('entity-select-change', entitySelectionChange);
|
||||
|
||||
// Handle selection confirm button click
|
||||
selectButton.click(event => {
|
||||
hide();
|
||||
if (entitySelection !== null) callback(entitySelection);
|
||||
});
|
||||
|
||||
// Show selector interface
|
||||
function show() {
|
||||
element.fadeIn(240);
|
||||
}
|
||||
|
||||
// Hide selector interface
|
||||
function hide() {
|
||||
element.fadeOut(240);
|
||||
}
|
||||
|
||||
// Listen to confirmation of entity selections (doubleclick)
|
||||
events.listen('entity-select-confirm', entity => {
|
||||
hide();
|
||||
callback(entity);
|
||||
});
|
||||
|
||||
// Show entity selector, Accessible globally, and store the callback
|
||||
window.showEntityLinkSelector = function(passedCallback) {
|
||||
show();
|
||||
callback = passedCallback;
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
|
||||
ngApp.directive('entitySelector', ['$http', '$sce', function ($http, $sce) {
|
||||
return {
|
||||
@@ -600,26 +768,60 @@ module.exports = function (ngApp, events) {
|
||||
// Add input for forms
|
||||
const input = element.find('[entity-selector-input]').first();
|
||||
|
||||
// Detect double click events
|
||||
var lastClick = 0;
|
||||
function isDoubleClick() {
|
||||
let now = Date.now();
|
||||
let answer = now - lastClick < 300;
|
||||
lastClick = now;
|
||||
return answer;
|
||||
}
|
||||
|
||||
// Listen to entity item clicks
|
||||
element.on('click', '.entity-list a', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
let item = $(this).closest('[data-entity-type]');
|
||||
itemSelect(item);
|
||||
itemSelect(item, isDoubleClick());
|
||||
});
|
||||
element.on('click', '[data-entity-type]', function(event) {
|
||||
itemSelect($(this));
|
||||
itemSelect($(this), isDoubleClick());
|
||||
});
|
||||
|
||||
// Select entity action
|
||||
function itemSelect(item) {
|
||||
function itemSelect(item, doubleClick) {
|
||||
let entityType = item.attr('data-entity-type');
|
||||
let entityId = item.attr('data-entity-id');
|
||||
let isSelected = !item.hasClass('selected');
|
||||
let isSelected = !item.hasClass('selected') || doubleClick;
|
||||
element.find('.selected').removeClass('selected').removeClass('primary-background');
|
||||
if (isSelected) item.addClass('selected').addClass('primary-background');
|
||||
let newVal = isSelected ? `${entityType}:${entityId}` : '';
|
||||
input.val(newVal);
|
||||
|
||||
if (!isSelected) {
|
||||
events.emit('entity-select-change', null);
|
||||
}
|
||||
|
||||
if (!doubleClick && !isSelected) return;
|
||||
|
||||
let link = item.find('.entity-list-item-link').attr('href');
|
||||
let name = item.find('.entity-list-item-name').text();
|
||||
|
||||
if (doubleClick) {
|
||||
events.emit('entity-select-confirm', {
|
||||
id: Number(entityId),
|
||||
name: name,
|
||||
link: link
|
||||
});
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
events.emit('entity-select-change', {
|
||||
id: Number(entityId),
|
||||
name: name,
|
||||
link: link
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get search url with correct types
|
||||
|
||||
@@ -18,9 +18,12 @@ window.baseUrl = function(path) {
|
||||
var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize', 'ui.sortable']);
|
||||
|
||||
// Global Event System
|
||||
var Events = {
|
||||
listeners: {},
|
||||
emit: function (eventName, eventData) {
|
||||
class EventManager {
|
||||
constructor() {
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
emit(eventName, eventData) {
|
||||
if (typeof this.listeners[eventName] === 'undefined') return this;
|
||||
var eventsToStart = this.listeners[eventName];
|
||||
for (let i = 0; i < eventsToStart.length; i++) {
|
||||
@@ -28,33 +31,35 @@ var Events = {
|
||||
event(eventData);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
listen: function (eventName, callback) {
|
||||
}
|
||||
|
||||
listen(eventName, callback) {
|
||||
if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = [];
|
||||
this.listeners[eventName].push(callback);
|
||||
return this;
|
||||
}
|
||||
};
|
||||
window.Events = Events;
|
||||
window.Events = new EventManager();
|
||||
|
||||
|
||||
var services = require('./services')(ngApp, Events);
|
||||
var directives = require('./directives')(ngApp, Events);
|
||||
var controllers = require('./controllers')(ngApp, Events);
|
||||
var services = require('./services')(ngApp, window.Events);
|
||||
var directives = require('./directives')(ngApp, window.Events);
|
||||
var controllers = require('./controllers')(ngApp, window.Events);
|
||||
|
||||
//Global jQuery Config & Extensions
|
||||
|
||||
// Smooth scrolling
|
||||
jQuery.fn.smoothScrollTo = function () {
|
||||
if (this.length === 0) return;
|
||||
$('body').animate({
|
||||
let scrollElem = document.documentElement.scrollTop === 0 ? document.body : document.documentElement;
|
||||
$(scrollElem).animate({
|
||||
scrollTop: this.offset().top - 60 // Adjust to change final scroll position top margin
|
||||
}, 800); // Adjust to change animations speed (ms)
|
||||
return this;
|
||||
};
|
||||
|
||||
// Making contains text expression not worry about casing
|
||||
$.expr[":"].contains = $.expr.createPseudo(function (arg) {
|
||||
jQuery.expr[":"].contains = $.expr.createPseudo(function (arg) {
|
||||
return function (elem) {
|
||||
return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
|
||||
};
|
||||
@@ -104,13 +109,14 @@ $(function () {
|
||||
var scrollTop = document.getElementById('back-to-top');
|
||||
var scrollTopBreakpoint = 1200;
|
||||
window.addEventListener('scroll', function() {
|
||||
if (!scrollTopShowing && document.body.scrollTop > scrollTopBreakpoint) {
|
||||
let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
|
||||
if (!scrollTopShowing && scrollTopPos > scrollTopBreakpoint) {
|
||||
scrollTop.style.display = 'block';
|
||||
scrollTopShowing = true;
|
||||
setTimeout(() => {
|
||||
scrollTop.style.opacity = 0.4;
|
||||
}, 1);
|
||||
} else if (scrollTopShowing && document.body.scrollTop < scrollTopBreakpoint) {
|
||||
} else if (scrollTopShowing && scrollTopPos < scrollTopBreakpoint) {
|
||||
scrollTop.style.opacity = 0;
|
||||
scrollTopShowing = false;
|
||||
setTimeout(() => {
|
||||
@@ -124,6 +130,27 @@ $(function () {
|
||||
$('.entity-list.compact').find('p').not('.empty-text').slideToggle(240);
|
||||
});
|
||||
|
||||
// Popup close
|
||||
$('.popup-close').click(function() {
|
||||
$(this).closest('.overlay').fadeOut(240);
|
||||
});
|
||||
$('.overlay').click(function(event) {
|
||||
if (!$(event.target).hasClass('overlay')) return;
|
||||
$(this).fadeOut(240);
|
||||
});
|
||||
|
||||
// Prevent markdown display link click redirect
|
||||
$('.markdown-display').on('click', 'a', function(event) {
|
||||
event.preventDefault();
|
||||
window.open($(this).attr('href'));
|
||||
});
|
||||
|
||||
// Detect IE for css
|
||||
if(navigator.userAgent.indexOf('MSIE')!==-1
|
||||
|| navigator.appVersion.indexOf('Trident/') > 0
|
||||
|| navigator.userAgent.indexOf('Safari') !== -1){
|
||||
$('body').addClass('flexbox-support');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,65 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Handle pasting images from clipboard.
|
||||
* @param e - event
|
||||
* @param editor - editor instance
|
||||
*/
|
||||
function editorPaste(e, editor) {
|
||||
if (!e.clipboardData) return
|
||||
let items = e.clipboardData.items;
|
||||
if (!items) return;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type.indexOf("image") === -1) return
|
||||
|
||||
let file = items[i].getAsFile();
|
||||
let formData = new FormData();
|
||||
let ext = 'png';
|
||||
let xhr = new XMLHttpRequest();
|
||||
|
||||
if (file.name) {
|
||||
let fileNameMatches = file.name.match(/\.(.+)$/);
|
||||
if (fileNameMatches) {
|
||||
ext = fileNameMatches[1];
|
||||
}
|
||||
}
|
||||
|
||||
let id = "image-" + Math.random().toString(16).slice(2);
|
||||
let loadingImage = window.baseUrl('/loading.gif');
|
||||
editor.execCommand('mceInsertContent', false, `<img src="${loadingImage}" id="${id}">`);
|
||||
|
||||
let remoteFilename = "image-" + Date.now() + "." + ext;
|
||||
formData.append('file', file, remoteFilename);
|
||||
formData.append('_token', document.querySelector('meta[name="token"]').getAttribute('content'));
|
||||
|
||||
xhr.open('POST', window.baseUrl('/images/gallery/upload'));
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
let result = JSON.parse(xhr.responseText);
|
||||
editor.dom.setAttrib(id, 'src', result.thumbs.display);
|
||||
} else {
|
||||
console.log('An error occurred uploading the image', xhr.responseText);
|
||||
editor.dom.remove(id);
|
||||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function registerEditorShortcuts(editor) {
|
||||
// Headers
|
||||
for (let i = 1; i < 5; i++) {
|
||||
editor.addShortcut('meta+' + i, '', ['FormatBlock', false, 'h' + i]);
|
||||
}
|
||||
|
||||
// Other block shortcuts
|
||||
editor.addShortcut('meta+q', '', ['FormatBlock', false, 'blockquote']);
|
||||
editor.addShortcut('meta+d', '', ['FormatBlock', false, 'p']);
|
||||
editor.addShortcut('meta+e', '', ['FormatBlock', false, 'pre']);
|
||||
editor.addShortcut('meta+shift+E', '', ['FormatBlock', false, 'code']);
|
||||
}
|
||||
|
||||
var mceOptions = module.exports = {
|
||||
selector: '#html-editor',
|
||||
content_css: [
|
||||
@@ -6,6 +68,8 @@ var mceOptions = module.exports = {
|
||||
],
|
||||
body_class: 'page-content',
|
||||
relative_urls: false,
|
||||
remove_script_host: false,
|
||||
document_base_url: window.baseUrl('/'),
|
||||
statusbar: false,
|
||||
menubar: false,
|
||||
paste_data_images: false,
|
||||
@@ -38,23 +102,41 @@ var mceOptions = module.exports = {
|
||||
alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
|
||||
},
|
||||
file_browser_callback: function (field_name, url, type, win) {
|
||||
window.ImageManager.showExternal(function (image) {
|
||||
win.document.getElementById(field_name).value = image.url;
|
||||
if ("createEvent" in document) {
|
||||
var evt = document.createEvent("HTMLEvents");
|
||||
evt.initEvent("change", false, true);
|
||||
win.document.getElementById(field_name).dispatchEvent(evt);
|
||||
} else {
|
||||
win.document.getElementById(field_name).fireEvent("onchange");
|
||||
}
|
||||
var html = '<a href="' + image.url + '" target="_blank">';
|
||||
html += '<img src="' + image.thumbs.display + '" alt="' + image.name + '">';
|
||||
html += '</a>';
|
||||
win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
|
||||
});
|
||||
|
||||
if (type === 'file') {
|
||||
window.showEntityLinkSelector(function(entity) {
|
||||
let originalField = win.document.getElementById(field_name);
|
||||
originalField.value = entity.link;
|
||||
$(originalField).closest('.mce-form').find('input').eq(2).val(entity.name);
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'image') {
|
||||
// Show image manager
|
||||
window.ImageManager.showExternal(function (image) {
|
||||
|
||||
// Set popover link input to image url then fire change event
|
||||
// to ensure the new value sticks
|
||||
win.document.getElementById(field_name).value = image.url;
|
||||
if ("createEvent" in document) {
|
||||
let evt = document.createEvent("HTMLEvents");
|
||||
evt.initEvent("change", false, true);
|
||||
win.document.getElementById(field_name).dispatchEvent(evt);
|
||||
} else {
|
||||
win.document.getElementById(field_name).fireEvent("onchange");
|
||||
}
|
||||
|
||||
// Replace the actively selected content with the linked image
|
||||
let html = `<a href="${image.url}" target="_blank">`;
|
||||
html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
|
||||
html += '</a>';
|
||||
win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
paste_preprocess: function (plugin, args) {
|
||||
var content = args.content;
|
||||
let content = args.content;
|
||||
if (content.indexOf('<img src="file://') !== -1) {
|
||||
args.content = '';
|
||||
}
|
||||
@@ -62,10 +144,14 @@ var mceOptions = module.exports = {
|
||||
extraSetups: [],
|
||||
setup: function (editor) {
|
||||
|
||||
for (var i = 0; i < mceOptions.extraSetups.length; i++) {
|
||||
// Run additional setup actions
|
||||
// Used by the angular side of things
|
||||
for (let i = 0; i < mceOptions.extraSetups.length; i++) {
|
||||
mceOptions.extraSetups[i](editor);
|
||||
}
|
||||
|
||||
registerEditorShortcuts(editor);
|
||||
|
||||
(function () {
|
||||
var wrap;
|
||||
|
||||
@@ -76,12 +162,11 @@ var mceOptions = module.exports = {
|
||||
editor.on('dragstart', function () {
|
||||
var node = editor.selection.getNode();
|
||||
|
||||
if (node.nodeName === 'IMG') {
|
||||
wrap = editor.dom.getParent(node, '.mceTemp');
|
||||
if (node.nodeName !== 'IMG') return;
|
||||
wrap = editor.dom.getParent(node, '.mceTemp');
|
||||
|
||||
if (!wrap && node.parentNode.nodeName === 'A' && !hasTextContent(node.parentNode)) {
|
||||
wrap = node.parentNode;
|
||||
}
|
||||
if (!wrap && node.parentNode.nodeName === 'A' && !hasTextContent(node.parentNode)) {
|
||||
wrap = node.parentNode;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -106,15 +191,15 @@ var mceOptions = module.exports = {
|
||||
});
|
||||
})();
|
||||
|
||||
// Image picker button
|
||||
// Custom Image picker button
|
||||
editor.addButton('image-insert', {
|
||||
title: 'My title',
|
||||
icon: 'image',
|
||||
tooltip: 'Insert an image',
|
||||
onclick: function () {
|
||||
window.ImageManager.showExternal(function (image) {
|
||||
var html = '<a href="' + image.url + '" target="_blank">';
|
||||
html += '<img src="' + image.thumbs.display + '" alt="' + image.name + '">';
|
||||
let html = `<a href="${image.url}" target="_blank">`;
|
||||
html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
|
||||
html += '</a>';
|
||||
editor.execCommand('mceInsertContent', false, html);
|
||||
});
|
||||
@@ -122,49 +207,8 @@ var mceOptions = module.exports = {
|
||||
});
|
||||
|
||||
// Paste image-uploads
|
||||
editor.on('paste', function (e) {
|
||||
if (e.clipboardData) {
|
||||
var items = e.clipboardData.items;
|
||||
if (items) {
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (items[i].type.indexOf("image") !== -1) {
|
||||
|
||||
var file = items[i].getAsFile();
|
||||
var formData = new FormData();
|
||||
var ext = 'png';
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
if (file.name) {
|
||||
var fileNameMatches = file.name.match(/\.(.+)$/);
|
||||
if (fileNameMatches) {
|
||||
ext = fileNameMatches[1];
|
||||
}
|
||||
}
|
||||
|
||||
var id = "image-" + Math.random().toString(16).slice(2);
|
||||
editor.execCommand('mceInsertContent', false, '<img src="/loading.gif" id="' + id + '">');
|
||||
|
||||
var remoteFilename = "image-" + Date.now() + "." + ext;
|
||||
formData.append('file', file, remoteFilename);
|
||||
formData.append('_token', document.querySelector('meta[name="token"]').getAttribute('content'));
|
||||
|
||||
xhr.open('POST', window.baseUrl('/images/gallery/upload'));
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
var result = JSON.parse(xhr.responseText);
|
||||
editor.dom.setAttrib(id, 'src', result.url);
|
||||
} else {
|
||||
console.log('An error occured uploading the image');
|
||||
console.log(xhr.responseText);
|
||||
editor.dom.remove(id);
|
||||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
editor.on('paste', function(event) {
|
||||
editorPaste(event, editor);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -100,3 +100,13 @@ $button-border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
background-color: #BBB;
|
||||
cursor: default;
|
||||
&:hover {
|
||||
background-color: #BBB;
|
||||
cursor: default;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.overlay {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
background-color: rgba(0, 0, 0, 0.333);
|
||||
position: fixed;
|
||||
z-index: 95536;
|
||||
width: 100%;
|
||||
@@ -10,26 +10,76 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.image-manager-body {
|
||||
.popup-body-wrap {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
background-color: #FFF;
|
||||
max-height: 90%;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
width: 1200px;
|
||||
height: auto;
|
||||
margin: 2% 5%;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
h1, h2, h3 {
|
||||
font-weight: 300;
|
||||
flex-direction: column;
|
||||
&.small {
|
||||
margin: 2% auto;
|
||||
width: 800px;
|
||||
max-width: 90%;
|
||||
}
|
||||
&:before {
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
//body.ie .popup-body {
|
||||
// min-height: 100%;
|
||||
//}
|
||||
|
||||
.corner-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
height: 40px;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.popup-header, .popup-footer {
|
||||
display: block !important;
|
||||
position: relative;
|
||||
height: 40px;
|
||||
flex: none !important;
|
||||
.popup-title {
|
||||
color: #FFF;
|
||||
padding: 8px $-m;
|
||||
}
|
||||
}
|
||||
body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
height: 444px;
|
||||
min-height: 444px;
|
||||
}
|
||||
#entity-selector-wrap .popup-body .form-group {
|
||||
margin: 0;
|
||||
}
|
||||
//body.ie #entity-selector-wrap .popup-body .form-group {
|
||||
// min-height: 60vh;
|
||||
//}
|
||||
|
||||
.image-manager-body {
|
||||
min-height: 70vh;
|
||||
}
|
||||
|
||||
#image-manager .dropzone-container {
|
||||
@@ -37,12 +87,6 @@
|
||||
border: 3px dashed #DDD;
|
||||
}
|
||||
|
||||
.image-manager-bottom {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.image-manager-list .image {
|
||||
display: block;
|
||||
position: relative;
|
||||
@@ -103,18 +147,13 @@
|
||||
|
||||
.image-manager-sidebar {
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
margin-left: 1px;
|
||||
padding: 0 $-l;
|
||||
padding: $-m $-l;
|
||||
overflow-y: auto;
|
||||
border-left: 1px solid #DDD;
|
||||
}
|
||||
|
||||
.image-manager-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
.dropzone-container {
|
||||
margin-top: $-m;
|
||||
}
|
||||
}
|
||||
|
||||
.image-manager-list {
|
||||
@@ -125,7 +164,6 @@
|
||||
.image-manager-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
.container {
|
||||
width: 100%;
|
||||
@@ -141,12 +179,13 @@
|
||||
* Copyright (c) 2012 Matias Meno <m@tias.me>
|
||||
*/
|
||||
.dz-message {
|
||||
font-size: 1.4em;
|
||||
font-size: 1.2em;
|
||||
line-height: 1.1;
|
||||
font-style: italic;
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
padding: $-xl $-m;
|
||||
padding: $-l $-m;
|
||||
transition: all ease-in-out 120ms;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,14 @@ body.flexbox {
|
||||
}
|
||||
}
|
||||
|
||||
.flex-child > div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
//body.ie .flex-child > div {
|
||||
// flex: 1 0 0px;
|
||||
//}
|
||||
|
||||
/** Rules for all columns */
|
||||
div[class^="col-"] img {
|
||||
max-width: 100%;
|
||||
@@ -39,6 +47,9 @@ div[class^="col-"] img {
|
||||
&.fluid {
|
||||
max-width: 100%;
|
||||
}
|
||||
&.medium {
|
||||
max-width: 992px;
|
||||
}
|
||||
&.small {
|
||||
max-width: 840px;
|
||||
}
|
||||
|
||||
@@ -155,6 +155,7 @@ form.search-box {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.faded span.faded-text {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #FFFFFF;
|
||||
height: 100%;
|
||||
@@ -9,6 +10,7 @@ html {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $text;
|
||||
font-size: $fs-m;
|
||||
@@ -19,13 +21,4 @@ body {
|
||||
|
||||
button {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
min-width: 100px;
|
||||
td, th {
|
||||
min-width: 10px;
|
||||
padding: 4px 6px;
|
||||
border: 1px solid #DDD;
|
||||
}
|
||||
}
|
||||
@@ -375,6 +375,9 @@ ul.pagination {
|
||||
.text-muted {
|
||||
color: #999;
|
||||
}
|
||||
li.padded {
|
||||
padding: $-xs $-m;
|
||||
}
|
||||
a {
|
||||
display: block;
|
||||
padding: $-xs $-m;
|
||||
@@ -384,10 +387,10 @@ ul.pagination {
|
||||
background-color: #EEE;
|
||||
}
|
||||
i {
|
||||
margin-right: $-m;
|
||||
margin-right: $-s;
|
||||
padding-right: 0;
|
||||
display: inline;
|
||||
width: 22px;
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
li.border-bottom {
|
||||
|
||||
@@ -20,6 +20,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.draft-notification {
|
||||
pointer-events: none;
|
||||
transform: scale(0);
|
||||
transition: transform ease-in-out 120ms;
|
||||
transform-origin: 50% 50%;
|
||||
&.visible {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.page-style.editor {
|
||||
padding: 0 !important;
|
||||
}
|
||||
@@ -56,9 +66,10 @@
|
||||
margin: $-m 0;
|
||||
}
|
||||
table {
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
hyphens: auto;
|
||||
table-layout: fixed;
|
||||
max-width: 100%;
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +249,7 @@
|
||||
}
|
||||
|
||||
.tag-display {
|
||||
margin: $-xl $-xs;
|
||||
margin: $-xl $-m;
|
||||
border: 1px solid #DDD;
|
||||
min-width: 180px;
|
||||
max-width: 320px;
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
table {
|
||||
min-width: 100px;
|
||||
max-width: 100%;
|
||||
thead {
|
||||
background-color: #F8F8F8;
|
||||
font-weight: 500;
|
||||
}
|
||||
td, th {
|
||||
min-width: 10px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #DDD;
|
||||
overflow: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
td p, th p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
table.table {
|
||||
width: 100%;
|
||||
@@ -9,6 +27,7 @@ table.table {
|
||||
border: none;
|
||||
padding: $-xs $-xs;
|
||||
vertical-align: middle;
|
||||
margin: 0;
|
||||
}
|
||||
th {
|
||||
font-weight: bold;
|
||||
@@ -18,14 +37,6 @@ table.table {
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
max-width: 100%;
|
||||
thead {
|
||||
background-color: #F8F8F8;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
table.no-style {
|
||||
td {
|
||||
border: 0;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
@import "animations";
|
||||
@import "tinymce";
|
||||
@import "highlightjs";
|
||||
@import "image-manager";
|
||||
@import "components";
|
||||
@import "header";
|
||||
@import "lists";
|
||||
@import "pages";
|
||||
@@ -72,7 +72,7 @@ body.dragging, body.dragging * {
|
||||
border-radius: 3px;
|
||||
box-shadow: $bs-med;
|
||||
z-index: 999999;
|
||||
display: table;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
max-width: 480px;
|
||||
i, span {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="book entity-list-item" data-entity-type="book" data-entity-id="{{$book->id}}">
|
||||
<h3 class="text-book"><a class="text-book" href="{{$book->getUrl()}}"><i class="zmdi zmdi-book"></i>{{$book->name}}</a></h3>
|
||||
<h3 class="text-book"><a class="text-book entity-list-item-link" href="{{$book->getUrl()}}"><i class="zmdi zmdi-book"></i><span class="entity-list-item-name">{{$book->name}}</span></a></h3>
|
||||
@if(isset($book->searchSnippet))
|
||||
<p class="text-muted">{!! $book->searchSnippet !!}</p>
|
||||
@else
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
var sortableOptions = {
|
||||
group: 'serialization',
|
||||
onDrop: function($item, container, _super) {
|
||||
var pageMap = buildPageMap();
|
||||
var pageMap = buildEntityMap();
|
||||
$('#sort-tree-input').val(JSON.stringify(pageMap));
|
||||
_super($item, container);
|
||||
},
|
||||
@@ -74,29 +74,42 @@
|
||||
$link.remove();
|
||||
});
|
||||
|
||||
function buildPageMap() {
|
||||
var pageMap = [];
|
||||
/**
|
||||
* Build up a mapping of entities with their ordering and nesting.
|
||||
* @returns {Array}
|
||||
*/
|
||||
function buildEntityMap() {
|
||||
var entityMap = [];
|
||||
var $lists = $('.sort-list');
|
||||
$lists.each(function(listIndex) {
|
||||
var list = $(this);
|
||||
var bookId = list.closest('[data-type="book"]').attr('data-id');
|
||||
var $childElements = list.find('[data-type="page"], [data-type="chapter"]');
|
||||
$childElements.each(function(childIndex) {
|
||||
var $directChildren = list.find('> [data-type="page"], > [data-type="chapter"]');
|
||||
$directChildren.each(function(directChildIndex) {
|
||||
var $childElem = $(this);
|
||||
var type = $childElem.attr('data-type');
|
||||
var parentChapter = false;
|
||||
if(type === 'page' && $childElem.closest('[data-type="chapter"]').length === 1) {
|
||||
parentChapter = $childElem.closest('[data-type="chapter"]').attr('data-id');
|
||||
}
|
||||
pageMap.push({
|
||||
id: $childElem.attr('data-id'),
|
||||
var childId = $childElem.attr('data-id');
|
||||
entityMap.push({
|
||||
id: childId,
|
||||
sort: directChildIndex,
|
||||
parentChapter: parentChapter,
|
||||
type: type,
|
||||
book: bookId
|
||||
});
|
||||
$chapterChildren = $childElem.find('[data-type="page"]').each(function(pageIndex) {
|
||||
var $chapterChild = $(this);
|
||||
entityMap.push({
|
||||
id: $chapterChild.attr('data-id'),
|
||||
sort: pageIndex,
|
||||
parentChapter: childId,
|
||||
type: 'page',
|
||||
book: bookId
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return pageMap;
|
||||
return entityMap;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
</a>
|
||||
<span class="text-muted"> » </span>
|
||||
@endif
|
||||
<a href="{{ $chapter->getUrl() }}" class="text-chapter">
|
||||
<i class="zmdi zmdi-collection-bookmark"></i>{{ $chapter->name }}
|
||||
<a href="{{ $chapter->getUrl() }}" class="text-chapter entity-list-item-link">
|
||||
<i class="zmdi zmdi-collection-bookmark"></i><span class="entity-list-item-name">{{ $chapter->name }}</span>
|
||||
</a>
|
||||
</h3>
|
||||
@if(isset($chapter->searchSnippet))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@extends('base')
|
||||
|
||||
@section('head')
|
||||
<script src="{{ baseUrl('/libs/tinymce/tinymce.min.js?ver=4.3.7') }}"></script>
|
||||
<script src="{{ baseUrl('/libs/tinymce/tinymce.min.js?ver=4.4.3') }}"></script>
|
||||
@stop
|
||||
|
||||
@section('body-class', 'flexbox')
|
||||
@@ -19,6 +19,14 @@
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
|
||||
@include('partials/entity-selector-popup')
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
@stop
|
||||
@@ -13,8 +13,9 @@
|
||||
</div>
|
||||
<div class="col-sm-4 faded text-center">
|
||||
|
||||
<div dropdown class="dropdown-container">
|
||||
<div dropdown class="dropdown-container draft-display">
|
||||
<a dropdown-toggle class="text-primary text-button"><span class="faded-text" ng-bind="draftText"></span> <i class="zmdi zmdi-more-vert"></i></a>
|
||||
<i class="zmdi zmdi-check-circle text-pos draft-notification" ng-class="{visible: draftUpdated}"></i>
|
||||
<ul>
|
||||
<li>
|
||||
<a ng-click="forceDraftSave()" class="text-pos"><i class="zmdi zmdi-save"></i>Save Draft</a>
|
||||
@@ -22,14 +23,25 @@
|
||||
<li ng-if="isNewPageDraft">
|
||||
<a href="{{ $model->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete Draft</a>
|
||||
</li>
|
||||
<li>
|
||||
<a type="button" ng-if="isUpdateDraft" ng-click="discardDraft()" class="text-neg"><i class="zmdi zmdi-close-circle"></i>Discard Draft</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4 faded">
|
||||
<div class="action-buttons" ng-cloak>
|
||||
<div dropdown class="dropdown-container">
|
||||
<a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-edit"></i> @{{(changeSummary | limitTo:16) + (changeSummary.length>16?'...':'') || 'Set Changelog'}}</a>
|
||||
<ul class="wide">
|
||||
<li class="padded">
|
||||
<p class="text-muted">Enter a brief description of the changes you've made</p>
|
||||
<input name="summary" id="summary-input" type="text" placeholder="Enter Changelog" ng-model="changeSummary" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button type="button" ng-if="isUpdateDraft" ng-click="discardDraft()" class="text-button text-neg"><i class="zmdi zmdi-close-circle"></i>Discard Draft</button>
|
||||
<button type="submit" id="save-button" class="text-button text-pos"><i class="zmdi zmdi-floppy"></i>Save Page</button>
|
||||
<button type="submit" id="save-button" class="text-button text-pos"><i class="zmdi zmdi-floppy"></i>Save Page</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,6 +74,8 @@
|
||||
<span class="float left">Editor</span>
|
||||
<div class="float right buttons">
|
||||
<button class="text-button" type="button" data-action="insertImage"><i class="zmdi zmdi-image"></i>Insert Image</button>
|
||||
|
|
||||
<button class="text-button" type="button" data-action="insertEntityLink"><i class="zmdi zmdi-link"></i>Insert Entity Link</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}">
|
||||
<h3>
|
||||
<a href="{{ $page->getUrl() }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ $page->name }}</a>
|
||||
<a href="{{ $page->getUrl() }}" class="text-page entity-list-item-link"><i class="zmdi zmdi-file-text"></i><span class="entity-list-item-name">{{ $page->name }}</span></a>
|
||||
</h3>
|
||||
|
||||
@if(isset($page->searchSnippet))
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</a>
|
||||
@endif
|
||||
<span class="sep">»</span>
|
||||
<a href="{{ $page->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a>
|
||||
<a href="{{ $page->getUrl() }}" class="text-page text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,45 +5,59 @@
|
||||
<div class="faded-small toolbar">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 faded">
|
||||
<div class="col-sm-12 faded">
|
||||
<div class="breadcrumbs">
|
||||
<a href="{{ $page->getUrl() }}" class="text-primary text-button"><i class="zmdi zmdi-arrow-left"></i>Back to page</a>
|
||||
<a href="{{ $page->book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}</a>
|
||||
@if($page->hasChapter())
|
||||
<span class="sep">»</span>
|
||||
<a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
|
||||
<i class="zmdi zmdi-collection-bookmark"></i>
|
||||
{{ $page->chapter->getShortName() }}
|
||||
</a>
|
||||
@endif
|
||||
<span class="sep">»</span>
|
||||
<a href="{{ $page->getUrl() }}" class="text-page text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 faded">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container small" ng-non-bindable>
|
||||
|
||||
<div class="container" ng-non-bindable>
|
||||
<h1>Page Revisions <span class="subheader">For "{{ $page->name }}"</span></h1>
|
||||
|
||||
@if(count($page->revisions) > 0)
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th width="40%">Name</th>
|
||||
<th colspan="2" width="20%">Created By</th>
|
||||
<th width="20%">Revision Date</th>
|
||||
<th width="20%">Actions</th>
|
||||
<th width="25%">Name</th>
|
||||
<th colspan="2" width="10%">Created By</th>
|
||||
<th width="15%">Revision Date</th>
|
||||
<th width="25%">Changelog</th>
|
||||
<th width="15%">Actions</th>
|
||||
</tr>
|
||||
@foreach($page->revisions as $revision)
|
||||
@foreach($page->revisions as $index => $revision)
|
||||
<tr>
|
||||
<td>{{$revision->name}}</td>
|
||||
<td>{{ $revision->name }}</td>
|
||||
<td style="line-height: 0;">
|
||||
@if($revision->createdBy)
|
||||
<img class="avatar" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{$revision->createdBy->name}}">
|
||||
<img class="avatar" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{ $revision->createdBy->name }}">
|
||||
@endif
|
||||
</td>
|
||||
<td> @if($revision->createdBy) {{$revision->createdBy->name}} @else Deleted User @endif</td>
|
||||
<td><small>{{$revision->created_at->format('jS F, Y H:i:s')}} <br> ({{$revision->created_at->diffForHumans()}})</small></td>
|
||||
<td>
|
||||
<a href="{{ $revision->getUrl() }}" target="_blank">Preview</a>
|
||||
<span class="text-muted"> | </span>
|
||||
<a href="{{ $revision->getUrl('/restore') }}">Restore</a>
|
||||
</td>
|
||||
<td> @if($revision->createdBy) {{ $revision->createdBy->name }} @else Deleted User @endif</td>
|
||||
<td><small>{{ $revision->created_at->format('jS F, Y H:i:s') }} <br> ({{ $revision->created_at->diffForHumans() }})</small></td>
|
||||
<td>{{ $revision->summary }}</td>
|
||||
@if ($index !== 0)
|
||||
<td>
|
||||
<a href="{{ $revision->getUrl() }}" target="_blank">Preview</a>
|
||||
<span class="text-muted"> | </span>
|
||||
<a href="{{ $revision->getUrl() }}/restore">Restore</a>
|
||||
</td>
|
||||
@else
|
||||
<td><a target="_blank" href="{{ $page->getUrl() }}"><i>Current Version</i></a></td>
|
||||
@endif
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<div class="container" id="page-show" ng-non-bindable>
|
||||
<div class="row">
|
||||
<div class="col-md-9 print-full-width">
|
||||
<div class="page-content anim fadeIn">
|
||||
<div class="page-content">
|
||||
|
||||
<div class="pointer-container" id="pointer">
|
||||
<div class="pointer anim">
|
||||
|
||||
14
resources/views/partials/entity-selector-popup.blade.php
Normal file
14
resources/views/partials/entity-selector-popup.blade.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<div id="entity-selector-wrap">
|
||||
<div class="overlay" entity-link-selector>
|
||||
<div class="popup-body small flex-child">
|
||||
<div class="popup-header primary-background">
|
||||
<div class="popup-title">Entity Select</div>
|
||||
<button type="button" class="corner-button neg button popup-close">x</button>
|
||||
</div>
|
||||
@include('partials/entity-selector', ['name' => 'entity-selector'])
|
||||
<div class="popup-footer">
|
||||
<button type="button" disabled="true" class="button entity-link-selector-confirm pos corner-button">Select</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,84 +1,94 @@
|
||||
<div id="image-manager" image-type="{{ $imageType }}" ng-controller="ImageManagerController" uploaded-to="{{ $uploaded_to or 0 }}">
|
||||
<div class="overlay anim-slide" ng-show="showing" ng-cloak ng-click="hide()">
|
||||
<div class="image-manager-body" ng-click="$event.stopPropagation()">
|
||||
<div class="overlay" ng-cloak ng-click="hide()">
|
||||
<div class="popup-body" ng-click="$event.stopPropagation()">
|
||||
|
||||
<div class="image-manager-content">
|
||||
<div ng-if="imageType === 'gallery'" class="container">
|
||||
<div class="image-manager-header row faded-small nav-tabs">
|
||||
<div class="col-xs-4 tab-item" title="View all images" ng-class="{selected: (view=='all')}" ng-click="setView('all')"><i class="zmdi zmdi-collection-image"></i> All</div>
|
||||
<div class="col-xs-4 tab-item" title="View images uploaded to this book" ng-class="{selected: (view=='book')}" ng-click="setView('book')"><i class="zmdi zmdi-book text-book"></i> Book</div>
|
||||
<div class="col-xs-4 tab-item" title="View images uploaded to this page" ng-class="{selected: (view=='page')}" ng-click="setView('page')"><i class="zmdi zmdi-file-text text-page"></i> Page</div>
|
||||
<div class="popup-header primary-background">
|
||||
<div class="popup-title">Image Select</div>
|
||||
<button class="popup-close neg corner-button button">x</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill image-manager-body">
|
||||
|
||||
<div class="image-manager-content">
|
||||
<div ng-if="imageType === 'gallery'" class="container">
|
||||
<div class="image-manager-header row faded-small nav-tabs">
|
||||
<div class="col-xs-4 tab-item" title="View all images" ng-class="{selected: (view=='all')}" ng-click="setView('all')"><i class="zmdi zmdi-collection-image"></i> All</div>
|
||||
<div class="col-xs-4 tab-item" title="View images uploaded to this book" ng-class="{selected: (view=='book')}" ng-click="setView('book')"><i class="zmdi zmdi-book text-book"></i> Book</div>
|
||||
<div class="col-xs-4 tab-item" title="View images uploaded to this page" ng-class="{selected: (view=='page')}" ng-click="setView('page')"><i class="zmdi zmdi-file-text text-page"></i> Page</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="view === 'all'" >
|
||||
<form ng-submit="searchImages()" class="contained-search-box">
|
||||
<input type="text" placeholder="Search by image name" ng-model="searchTerm">
|
||||
<button ng-class="{active: searching}" title="Clear Search" type="button" ng-click="cancelSearch()" class="text-button cancel"><i class="zmdi zmdi-close-circle-o"></i></button>
|
||||
<button title="Search" class="text-button" type="submit"><i class="zmdi zmdi-search"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="image-manager-list">
|
||||
<div ng-repeat="image in images">
|
||||
<div class="image anim fadeIn" ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}"
|
||||
ng-class="{selected: (image==selectedImage)}" ng-click="imageSelect(image)">
|
||||
<img ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}">
|
||||
<div class="image-meta">
|
||||
<span class="name" ng-bind="image.name"></span>
|
||||
<span class="date">Uploaded @{{ getDate(image.created_at) | date:'mediumDate' }}</span>
|
||||
<div ng-show="view === 'all'" >
|
||||
<form ng-submit="searchImages()" class="contained-search-box">
|
||||
<input type="text" placeholder="Search by image name" ng-model="searchTerm">
|
||||
<button ng-class="{active: searching}" title="Clear Search" type="button" ng-click="cancelSearch()" class="text-button cancel"><i class="zmdi zmdi-close-circle-o"></i></button>
|
||||
<button title="Search" class="text-button" type="submit"><i class="zmdi zmdi-search"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="image-manager-list">
|
||||
<div ng-repeat="image in images">
|
||||
<div class="image anim fadeIn" ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}"
|
||||
ng-class="{selected: (image==selectedImage)}" ng-click="imageSelect(image)">
|
||||
<img ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}">
|
||||
<div class="image-meta">
|
||||
<span class="name" ng-bind="image.name"></span>
|
||||
<span class="date">Uploaded @{{ getDate(image.created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="load-more" ng-show="hasMore" ng-click="fetchData()">Load More</div>
|
||||
</div>
|
||||
<div class="load-more" ng-show="hasMore" ng-click="fetchData()">Load More</div>
|
||||
</div>
|
||||
|
||||
<div class="image-manager-sidebar">
|
||||
<div class="inner">
|
||||
|
||||
<div class="image-manager-details anim fadeIn" ng-show="selectedImage">
|
||||
|
||||
<form ng-submit="saveImageDetails($event)">
|
||||
<div>
|
||||
<a ng-href="@{{selectedImage.url}}" target="_blank" style="display: block;">
|
||||
<img ng-src="@{{selectedImage.thumbs.gallery}}" ng-attr-alt="@{{selectedImage.title}}" ng-attr-title="@{{selectedImage.name}}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">Image Name</label>
|
||||
<input type="text" id="name" name="name" ng-model="selectedImage.name">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div ng-show="dependantPages">
|
||||
<p class="text-neg text-small">
|
||||
This image is used in the pages below, Click delete again to confirm you want to delete
|
||||
this image.
|
||||
</p>
|
||||
<ul class="text-neg">
|
||||
<li ng-repeat="page in dependantPages">
|
||||
<a ng-href="@{{ page.url }}" target="_blank" class="text-neg" ng-bind="page.name"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<form class="float left" ng-submit="deleteImage($event)">
|
||||
<button class="button icon neg"><i class="zmdi zmdi-delete"></i></button>
|
||||
</form>
|
||||
<button class="button pos anim fadeIn float right" ng-show="selectedImage" ng-click="selectButtonClick()">
|
||||
<i class="zmdi zmdi-square-right"></i>Select Image
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<button class="neg button image-manager-close" ng-click="hide()">x</button>
|
||||
|
||||
<div class="image-manager-sidebar">
|
||||
<h2>Images</h2>
|
||||
<drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
|
||||
<div class="image-manager-details anim fadeIn" ng-show="selectedImage">
|
||||
|
||||
<hr class="even">
|
||||
|
||||
<form ng-submit="saveImageDetails($event)">
|
||||
<div>
|
||||
<a ng-href="@{{selectedImage.url}}" target="_blank" style="display: block;">
|
||||
<img ng-src="@{{selectedImage.thumbs.gallery}}" ng-attr-alt="@{{selectedImage.title}}" ng-attr-title="@{{selectedImage.name}}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">Image Name</label>
|
||||
<input type="text" id="name" name="name" ng-model="selectedImage.name">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr class="even">
|
||||
|
||||
<div ng-show="dependantPages">
|
||||
<p class="text-neg text-small">
|
||||
This image is used in the pages below, Click delete again to confirm you want to delete
|
||||
this image.
|
||||
</p>
|
||||
<ul class="text-neg">
|
||||
<li ng-repeat="page in dependantPages">
|
||||
<a ng-href="@{{ page.url }}" target="_blank" class="text-neg" ng-bind="page.name"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<form ng-submit="deleteImage($event)">
|
||||
<button class="button neg"><i class="zmdi zmdi-delete"></i>Delete Image</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="image-manager-bottom">
|
||||
<button class="button pos anim fadeIn" ng-show="selectedImage" ng-click="selectButtonClick()">
|
||||
<i class="zmdi zmdi-square-right"></i>Select Image
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,6 +76,14 @@ class EntitySearchTest extends TestCase
|
||||
->see('Chapter Search Results')->seeInElement('.entity-list', $chapter->name);
|
||||
}
|
||||
|
||||
public function test_search_quote_term_preparation()
|
||||
{
|
||||
$termString = '"192" cat "dog hat"';
|
||||
$repo = $this->app[\BookStack\Repos\EntityRepo::class];
|
||||
$preparedTerms = $repo->prepareSearchTerms($termString);
|
||||
$this->assertTrue($preparedTerms === ['"192"','"dog hat"', 'cat']);
|
||||
}
|
||||
|
||||
public function test_books_search_listing()
|
||||
{
|
||||
$book = \BookStack\Book::all()->last();
|
||||
|
||||
@@ -218,13 +218,24 @@ class EntityTest extends TestCase
|
||||
|
||||
public function test_old_page_slugs_redirect_to_new_pages()
|
||||
{
|
||||
$page = \BookStack\Page::all()->first();
|
||||
$page = \BookStack\Page::first();
|
||||
$pageUrl = $page->getUrl();
|
||||
$newPageUrl = '/books/' . $page->book->slug . '/page/super-test-page';
|
||||
// Need to save twice since revisions are not generated in seeder.
|
||||
$this->asAdmin()->visit($pageUrl)
|
||||
->clickInElement('#content', 'Edit')
|
||||
->type('super test', '#name')
|
||||
->press('Save Page');
|
||||
|
||||
$page = \BookStack\Page::first();
|
||||
$pageUrl = $page->getUrl();
|
||||
|
||||
// Second Save
|
||||
$this->visit($pageUrl)
|
||||
->clickInElement('#content', 'Edit')
|
||||
->type('super test page', '#name')
|
||||
->press('Save Page')
|
||||
// Check redirect
|
||||
->seePageIs($newPageUrl)
|
||||
->visit($pageUrl)
|
||||
->seePageIs($newPageUrl);
|
||||
|
||||
Reference in New Issue
Block a user