Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/Activity/Notifications/NotificationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use BookStack\Activity\Models\Activity;
use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Notifications\Handlers\CommentCreationNotificationHandler;
use BookStack\Activity\Notifications\Handlers\CommentMentionNotificationHandler;
use BookStack\Activity\Notifications\Handlers\NotificationHandler;
use BookStack\Activity\Notifications\Handlers\PageCreationNotificationHandler;
use BookStack\Activity\Notifications\Handlers\PageUpdateNotificationHandler;
Expand Down Expand Up @@ -48,5 +49,7 @@
$this->registerHandler(ActivityType::PAGE_CREATE, PageCreationNotificationHandler::class);
$this->registerHandler(ActivityType::PAGE_UPDATE, PageUpdateNotificationHandler::class);
$this->registerHandler(ActivityType::COMMENT_CREATE, CommentCreationNotificationHandler::class);
$this->registerHandler(ActivityType::COMMENT_CREATE, CommentMentionNotificationHandler::class);

Check failure on line 52 in app/Activity/Notifications/NotificationManager.php

View workflow job for this annotation

GitHub Actions / build

Class BookStack\Activity\Notifications\Handlers\CommentMentionNotificationHandler not found.

Check failure on line 52 in app/Activity/Notifications/NotificationManager.php

View workflow job for this annotation

GitHub Actions / build

Class BookStack\Activity\Notifications\Handlers\CommentMentionNotificationHandler not found.
$this->registerHandler(ActivityType::COMMENT_UPDATE, CommentMentionNotificationHandler::class);

Check failure on line 53 in app/Activity/Notifications/NotificationManager.php

View workflow job for this annotation

GitHub Actions / build

Class BookStack\Activity\Notifications\Handlers\CommentMentionNotificationHandler not found.

Check failure on line 53 in app/Activity/Notifications/NotificationManager.php

View workflow job for this annotation

GitHub Actions / build

Class BookStack\Activity\Notifications\Handlers\CommentMentionNotificationHandler not found.
}
}
28 changes: 28 additions & 0 deletions app/Activity/Tools/MentionParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace BookStack\Activity\Tools;

use BookStack\Util\HtmlDocument;
use DOMElement;

class MentionParser
{
public function parseUserIdsFromHtml(string $html): array
{
$doc = new HtmlDocument($html);

$ids = [];
$mentionLinks = $doc->queryXPath('//a[@data-mention-user-id]');

foreach ($mentionLinks as $link) {
if ($link instanceof DOMElement) {
$id = intval($link->getAttribute('data-mention-user-id'));
if ($id > 0) {
$ids[] = $id;
}
}
}

return array_values(array_unique($ids));
}
}
2 changes: 2 additions & 0 deletions app/App/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace BookStack\App\Providers;

use BookStack\Access\SocialDriverManager;
use BookStack\Activity\Models\Comment;
use BookStack\Activity\Tools\ActivityLogger;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
Expand Down Expand Up @@ -73,6 +74,7 @@ public function boot(): void
'book' => Book::class,
'chapter' => Chapter::class,
'page' => Page::class,
'comment' => Comment::class,
]);
}
}
1 change: 1 addition & 0 deletions app/Config/setting-defaults.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
'bookshelves_view_type' => env('APP_VIEWS_BOOKSHELVES', 'grid'),
'bookshelf_view_type' => env('APP_VIEWS_BOOKSHELF', 'grid'),
'books_view_type' => env('APP_VIEWS_BOOKS', 'grid'),
'notifications#comment-mentions' => true,
],

];
7 changes: 6 additions & 1 deletion app/Settings/UserNotificationPreferences.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ public function notifyOnCommentReplies(): bool
return $this->getNotificationSetting('comment-replies');
}

public function notifyOnCommentMentions(): bool
{
return $this->getNotificationSetting('comment-mentions');
}

public function updateFromSettingsArray(array $settings)
{
$allowList = ['own-page-changes', 'own-page-comments', 'comment-replies'];
$allowList = ['own-page-changes', 'own-page-comments', 'comment-replies', 'comment-mentions'];
foreach ($settings as $setting => $status) {
if (!in_array($setting, $allowList)) {
continue;
Expand Down
38 changes: 37 additions & 1 deletion app/Users/Controllers/UserSearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use BookStack\Http\Controller;
use BookStack\Permissions\Permission;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;

class UserSearchController extends Controller
Expand Down Expand Up @@ -34,8 +35,43 @@ public function forSelect(Request $request)
$query->where('name', 'like', '%' . $search . '%');
}

/** @var Collection<User> $users */
$users = $query->get();

return view('form.user-select-list', [
'users' => $query->get(),
'users' => $users,
]);
}

/**
* Search users in the system, with the response formatted
* for use in a list of mentions.
*/
public function forMentions(Request $request)
{
$hasPermission = !user()->isGuest() && (
userCan(Permission::CommentCreateAll)
|| userCan(Permission::CommentUpdate)
);

if (!$hasPermission) {
$this->showPermissionError();
}

$search = $request->get('search', '');
$query = User::query()
->orderBy('name', 'asc')
->take(20);

if (!empty($search)) {
$query->where('name', 'like', '%' . $search . '%');
}

/** @var Collection<User> $users */
$users = $query->get();

return view('form.user-mention-list', [
'users' => $users,
]);
}
}
2 changes: 1 addition & 1 deletion app/Util/HtmlDescriptionFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class HtmlDescriptionFilter
*/
protected static array $allowedAttrsByElements = [
'p' => [],
'a' => ['href', 'title', 'target'],
'a' => ['href', 'title', 'target', 'data-mention-user-id'],
'ol' => [],
'ul' => [],
'li' => [],
Expand Down
50 changes: 41 additions & 9 deletions dev/build/esbuild.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
#!/usr/bin/env node

const esbuild = require('esbuild');
const path = require('path');
const fs = require('fs');
import * as esbuild from 'esbuild';
import * as path from 'node:path';
import * as fs from 'node:fs';
import * as process from "node:process";


// Check if we're building for production
// (Set via passing `production` as first argument)
const isProd = process.argv[2] === 'production';
const mode = process.argv[2];
const isProd = mode === 'production';
const __dirname = import.meta.dirname;

// Gather our input files
const entryPoints = {
Expand All @@ -17,11 +21,16 @@ const entryPoints = {
wysiwyg: path.join(__dirname, '../../resources/js/wysiwyg/index.ts'),
};

// Watch styles so we can reload on change
if (mode === 'watch') {
entryPoints['styles-dummy'] = path.join(__dirname, '../../public/dist/styles.css');
}

// Locate our output directory
const outdir = path.join(__dirname, '../../public/dist');

// Build via esbuild
esbuild.build({
// Define the options for esbuild
const options = {
bundle: true,
metafile: true,
entryPoints,
Expand All @@ -33,6 +42,7 @@ esbuild.build({
minify: isProd,
logLevel: 'info',
loader: {
'.html': 'copy',
'.svg': 'text',
},
absWorkingDir: path.join(__dirname, '../..'),
Expand All @@ -45,6 +55,28 @@ esbuild.build({
js: '// See the "/licenses" URI for full package license details',
css: '/* See the "/licenses" URI for full package license details */',
},
}).then(result => {
fs.writeFileSync('esbuild-meta.json', JSON.stringify(result.metafile));
}).catch(() => process.exit(1));
};

if (mode === 'watch') {
options.inject = [
path.join(__dirname, './livereload.js'),
];
}

const ctx = await esbuild.context(options);

if (mode === 'watch') {
// Watch for changes and rebuild on change
ctx.watch({});
let {hosts, port} = await ctx.serve({
servedir: path.join(__dirname, '../../public'),
cors: {
origin: '*',
}
});
} else {
// Build with meta output for analysis
ctx.rebuild().then(result => {
fs.writeFileSync('esbuild-meta.json', JSON.stringify(result.metafile));
}).catch(() => process.exit(1));
}
35 changes: 35 additions & 0 deletions dev/build/livereload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
if (!window.__dev_reload_listening) {
listen();
window.__dev_reload_listening = true;
}


function listen() {
console.log('Listening for livereload events...');
new EventSource("http://127.0.0.1:8000/esbuild").addEventListener('change', e => {
const { added, removed, updated } = JSON.parse(e.data);

if (!added.length && !removed.length && updated.length > 0) {
const updatedPath = updated.filter(path => path.endsWith('.css'))[0]
if (!updatedPath) return;

const links = [...document.querySelectorAll("link[rel='stylesheet']")];
for (const link of links) {
const url = new URL(link.href);
const name = updatedPath.replace('-dummy', '');

if (url.pathname.endsWith(name)) {
const next = link.cloneNode();
next.href = name + '?version=' + Math.random().toString(36).slice(2);
next.onload = function() {
link.remove();
};
link.after(next);
return
}
}
}

location.reload()
});
}
2 changes: 2 additions & 0 deletions lang/en/notifications.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
'updated_page_subject' => 'Updated page: :pageName',
'updated_page_intro' => 'A page has been updated in :appName:',
'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.',
'comment_mention_subject' => 'You were mentioned in a comment on :pageName',
'comment_mention_intro' => 'You were mentioned in a comment on :appName:',

'detail_page_name' => 'Page Name:',
'detail_page_path' => 'Page Path:',
Expand Down
1 change: 1 addition & 0 deletions lang/en/preferences.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.',
'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own',
'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own',
'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment',
'notifications_opt_comment_replies' => 'Notify upon replies to my comments',
'notifications_save' => 'Save Preferences',
'notifications_update_success' => 'Notification preferences have been updated!',
Expand Down
64 changes: 0 additions & 64 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"private": true,
"type": "module",
"scripts": {
"build:css:dev": "sass ./resources/sass:./public/dist --embed-sources",
"build:css:watch": "sass ./resources/sass:./public/dist --watch --embed-sources",
"build:css:production": "sass ./resources/sass:./public/dist -s compressed",
"build:js:dev": "node dev/build/esbuild.js",
"build:js:watch": "chokidar --initial \"./resources/**/*.js\" \"./resources/**/*.mjs\" \"./resources/**/*.ts\" -c \"npm run build:js:dev\"",
"build:js:watch": "node dev/build/esbuild.js watch",
"build:js:production": "node dev/build/esbuild.js production",
"build": "npm-run-all --parallel build:*:dev",
"production": "npm-run-all --parallel build:*:production",
"dev": "npm-run-all --parallel watch livereload",
"dev": "npm-run-all --parallel build:*:watch",
"watch": "npm-run-all --parallel build:*:watch",
"livereload": "livereload ./public/dist/",
"permissions": "chown -R $USER:$USER bootstrap/cache storage public/uploads",
"lint": "eslint \"resources/**/*.js\" \"resources/**/*.mjs\"",
"fix": "eslint --fix \"resources/**/*.js\" \"resources/**/*.mjs\"",
Expand All @@ -29,7 +29,6 @@
"eslint-plugin-import": "^2.32.0",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"livereload": "^0.10.3",
"npm-run-all": "^4.1.5",
"sass": "^1.94.2",
"ts-jest": "^29.4.5",
Expand Down
Loading
Loading