Compare commits

...

38 Commits

Author SHA1 Message Date
Dan Brown
f7b01ae53d Updated assets for release v0.12.1 2016-09-06 20:50:15 +01:00
Dan Brown
d704e1dbba Merge branch 'master' into release 2016-09-06 20:49:15 +01:00
Dan Brown
393f6047f2 Updated table styling to ideally by more predictable
Set styles to be fixed and not cut text.
2016-09-06 20:47:34 +01:00
Dan Brown
d94fc5b694 Fixed table cell p tag spacing and de-duped editor shortcuts
Also changed shortcuts to be better compatible with OSX by using the
command key.
2016-09-06 19:50:47 +01:00
Dan Brown
f06770d91d Updated TinyMCE version from 4.3.7 to 4.4.3 2016-09-06 19:29:09 +01:00
Dan Brown
ef2ff5e093 Updated assets for release v0.12 2016-09-05 19:49:42 +01:00
Dan Brown
7caed3b0db Merge branch 'master' into release 2016-09-05 19:35:21 +01:00
Dan Brown
11960d9d3a Cleaned page form JS & spaced tag box
As per #174
2016-09-05 19:33:14 +01:00
Dan Brown
bbd8fff021 Fixed bad image base-urls and forced tinyMCE to use absolute
Also ensured image file existance is checked during base64 conversion
during exports.
Closes #171.
2016-09-03 19:24:58 +01:00
Dan Brown
ec17bd8608 Improved Exception handling, Removed npm requirement for testing 2016-09-03 12:08:58 +01:00
Dan Brown
0dbb8babee Merge branch 'page_link_selector' 2016-09-03 10:40:27 +01:00
Dan Brown
b14e9fc619 Fixed some cross browser flexbox popup issues
Set min height for poor IE & safari flexbox support.
Fixes #105.
2016-09-03 10:32:14 +01:00
Dan Brown
63c6d3478d Added image paste and drop to markdown editor
Only currently tested in chrome.
Closes #128
2016-09-02 21:18:48 +01:00
Dan Brown
781f0e7887 Added draft save indicator and fixed notification positions 2016-09-02 19:26:12 +01:00
Dan Brown
23e014cb25 Added link selector to markdown editor 2016-09-02 18:54:26 +01:00
Dan Brown
5b64358ef1 Added link selector interface to WYSIWYG editor 2016-09-01 20:36:22 +01:00
Dan Brown
56df64063d Updated popup design and started integrating link selector
Extracted design and base styles from image manager into generic popup
classes that can be used by other app components such as the new
popup Book/Chapter/Page link selector
2016-08-30 20:05:59 +01:00
Dan Brown
3f81eba13b Updated travis testing to work with new helper configuration (#175)
* Updated travis to call phpunit globally rather then booting application first
2016-08-27 11:27:23 +01:00
Dan Brown
7973412c29 Improved sort efficiency by a factor of 10
Fixes #145
2016-08-26 20:20:58 +01:00
Dan Brown
f83de5f834 Fixed single word quoted search terms
Fixes #170
2016-08-25 17:17:26 +01:00
Dan Brown
f2ceba978a Removed animation from page content.
Prevents issues with browsers that do not support
animaton-fill-direction.
Fixes #173.
2016-08-25 16:47:25 +01:00
Dan Brown
96c074bb56 Merge fixes from branch 'v0.11' 2016-08-21 15:02:37 +01:00
Dan Brown
f8ad820281 Merge pull request #166 from poppahorse/patch-1
Applied baseUrl to login redirect
2016-08-21 13:44:36 +01:00
Chris
632ecc668f Applied baseUrl to login redirect 2016-08-15 15:07:45 +01:00
Dan Brown
92d393537c Merge branch 'v0.11' 2016-08-14 13:09:44 +01:00
Dan Brown
42976ca48c Fixed revision-based redirect on new pages 2016-07-26 18:16:40 +01:00
Dan Brown
d05e85efa9 Fixed back-to-top button on firefox. Fixes #153. 2016-07-26 18:03:10 +01:00
Dan Brown
547e117760 Changed issue template to use md extension 2016-07-26 17:46:09 +01:00
Nick Walke
50a5d3c546 Added issue template 2016-07-21 10:04:06 -05:00
Dan Brown
3fd82200cc Added WYSIWYG editor shortcuts 2016-07-12 21:11:48 +01:00
Dan Brown
7215392784 Changed when revisions are saved and update changelog input
Revisions are now saved when te page content is originally saved whereas before they were saved on the next update to the page.
2016-07-10 12:12:52 +01:00
Dan Brown
8a9a8dfae5 Merge branch 'summary' of git://github.com/younes0/BookStack into younes0-summary 2016-07-10 10:42:47 +01:00
Dan Brown
c44314def3 Added check for s3 bucket name to choose shortest url 2016-07-10 10:28:05 +01:00
Dan Brown
8b899a9cf0 Merge branch 'patch-1' of git://github.com/younes0/BookStack into younes0-patch-1 2016-07-10 10:15:38 +01:00
Dan Brown
0ebdfa4825 Merge pull request #144 from younes0/patch-2
set uploaded images visibility public (relevant for S3 storage)
2016-07-10 10:14:19 +01:00
Younes El Biache
32a06f119b set uploaded files public visibliity (relevant for S3 storage) 2016-07-09 15:26:53 +02:00
Younes El Biache
ec30864ce5 shorter amazon S3 url 2016-07-09 14:33:37 +02:00
Younès EL BIACHE
6bc72e157a edit summary 2016-07-07 20:53:43 +02:00
71 changed files with 1025 additions and 398 deletions

11
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,11 @@
### For Feature Requests
Desired Feature:
### For Bug Reports
PHP Version:
MySQL Version:
Expected Behavior:
Actual Behavior:

View File

@@ -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

View File

@@ -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('/&quot;.*?&quot;/', $term)) {
$term = str_replace('&quot;', '', $term);
$exactTerms[] = '%' . $term . '%';
$term = '"' . $term . '"';
} else {
@@ -206,5 +207,5 @@ class Entity extends Ownable
return $search->orderBy($orderBy, 'desc');
}
}

View File

@@ -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;
}
}

View File

@@ -1,5 +1,3 @@
<?php namespace BookStack\Exceptions;
use Exception;
class PrettyException extends Exception {}
class PrettyException extends \Exception {}

View File

@@ -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());
}

View File

@@ -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));

View File

@@ -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

View File

@@ -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
{

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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
}
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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.");
}
/**

View File

@@ -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');
});
}
}

View File

@@ -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"/>

View File

@@ -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"
}

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

View File

@@ -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

View File

@@ -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)}})});

View File

@@ -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

View File

@@ -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});

View File

@@ -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

View File

@@ -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

View File

@@ -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))})})});

View File

@@ -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?"&#215;":"")+"</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?"&#215;":"")+"</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})});

View File

@@ -36,7 +36,7 @@
<glyph unicode="&#xe01a;" 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="&#xe01b;" 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="&#xe01c;" glyph-name="hr" d="M64 512h896v-128h-896z" />
<glyph unicode="&#xe01d;" 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="&#xe01d;" 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="&#xe01e;" 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="&#xe01f;" 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="&#xe020;" 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

File diff suppressed because one or more lines are too long

View File

@@ -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();
};

View File

@@ -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 + ('![](http://'.length);
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 = "![" + image.name + "](" + image.url + ")";
let mdImageText = "![" + image.name + "](" + image.thumbs.display + ")";
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

View File

@@ -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');
}
});

View File

@@ -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);
});
}
};

View File

@@ -100,3 +100,13 @@ $button-border-radius: 2px;
}
}
.button[disabled] {
background-color: #BBB;
cursor: default;
&:hover {
background-color: #BBB;
cursor: default;
box-shadow: none;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -155,6 +155,7 @@ form.search-box {
text-decoration: none;
}
}
}
.faded span.faded-text {

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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

View File

@@ -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;
}
});

View File

@@ -6,8 +6,8 @@
</a>
<span class="text-muted">&nbsp;&nbsp;&raquo;&nbsp;&nbsp;</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))

View File

@@ -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

View File

@@ -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>&nbsp; <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>
&nbsp;|&nbsp;
<button class="text-button" type="button" data-action="insertEntityLink"><i class="zmdi zmdi-link"></i>Insert Entity Link</button>
</div>
</div>

View File

@@ -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))

View File

@@ -16,7 +16,7 @@
</a>
@endif
<span class="sep">&raquo;</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>

View File

@@ -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">&raquo;</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">&raquo;</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">&nbsp;|&nbsp;</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">&nbsp;|&nbsp;</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>

View File

@@ -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">

View 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>

View File

@@ -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>

View File

@@ -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();

View File

@@ -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);