Compare commits

...

1 Commits

Author SHA1 Message Date
1dca0951e8 🚧 Commence la personnalisation de l'affichage des pages
Pour #12
2022-04-19 16:55:18 +02:00
18 changed files with 695 additions and 24 deletions

View File

@ -45,11 +45,13 @@ class PageController extends Controller
} }
}, Storage::disk('pages')->files(Auth::user()->getAuthIdentifier()))); }, Storage::disk('pages')->files(Auth::user()->getAuthIdentifier())));
/** @var \App\Models\User $user */
$user = Auth::user(); $user = Auth::user();
return view('pages.index', [ return view('pages.index', [
'start_date'=> min((new DateTime())->sub(new DateInterval('P1Y')), $user->created_at), 'start_date' => min((new DateTime())->sub(new DateInterval('P1Y')), $user->created_at),
'checkword' => $user->checkword, 'checkword' => $user->checkword,
'settings' => json_encode($user->getSettings()),
'pages' => $pages, 'pages' => $pages,
]); ]);
} }
@ -124,7 +126,7 @@ class PageController extends Controller
*/ */
public function edit($id) public function edit($id)
{ {
throw new MethodNotAllowedHttpException('You can\'t edit a page.'); throw new MethodNotAllowedHttpException(['GET'], 'You can\'t edit a page.');
} }
/** /**
@ -137,7 +139,7 @@ class PageController extends Controller
*/ */
public function update(Request $request, $id) public function update(Request $request, $id)
{ {
throw new MethodNotAllowedHttpException('You can\'t edit a page.'); throw new MethodNotAllowedHttpException(['GET'], 'You can\'t edit a page.');
} }
/** /**

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\User\SettingsRequest;
use App\Http\Requests\User\WordCheckRequest; use App\Http\Requests\User\WordCheckRequest;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -23,4 +24,26 @@ class UserController extends Controller
return response()->json(['success' => true]); return response()->json(['success' => true]);
} }
public function settings()
{
return view('user.settings', [
'settings' => Auth::user()->getSettings(),
]);
}
public function storeSettings(SettingsRequest $request)
{
$validated = $request->validated();
$user = User::where('id', Auth::user()->getAuthIdentifier())->firstOrFail();
foreach ($validated as $name => $value) {
$user->{$name} = $value;
}
$user->save();
Auth::setUser($user);
$request->session()->flash('status', __('Settings saved!'));
return redirect()->route('user.settings');
}
} }

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest;
class SettingsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'text_color' => ['required', 'string', 'max:7'],
'background_color' => ['required', 'string', 'max:7'],
'font' => ['required', 'string', 'max:255'],
'font_size' => ['required', 'numeric'],
'line_spacing' => ['required', 'numeric'],
];
}
}

View File

@ -33,6 +33,14 @@ class User extends Authenticatable
'remember_token', 'remember_token',
]; ];
protected $settings = [
'text_color',
'background_color',
'font',
'font_size',
'line_spacing',
];
/** /**
* The attributes that should be cast. * The attributes that should be cast.
* *
@ -41,4 +49,17 @@ class User extends Authenticatable
protected $casts = [ protected $casts = [
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
]; ];
/**
* @return array
*/
public function getSettings()
{
$settings = [];
foreach ($this->settings as $setting) {
$settings[$setting] = $this->{$setting};
}
return $settings;
}
} }

View File

@ -6,13 +6,15 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^7.3|^8.0", "php": "^7.3|^8.0",
"doctrine/dbal": "^3.3",
"fruitcake/laravel-cors": "^2.0", "fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1", "guzzlehttp/guzzle": "^7.0.1",
"kzykhys/yaml-front-matter": "^1.0", "kzykhys/yaml-front-matter": "^1.0",
"laravel/framework": "^9.0", "laravel/framework": "^9.0",
"laravel/sanctum": "^2.11", "laravel/sanctum": "^2.11",
"laravel/tinker": "^2.5", "laravel/tinker": "^2.5",
"laravel/ui": "^3.4" "laravel/ui": "^3.4",
"ext-json": "*"
}, },
"require-dev": { "require-dev": {
"spatie/laravel-ignition": "^1.0", "spatie/laravel-ignition": "^1.0",

398
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "af7035cb5f906bc34893e53d97e6656f", "content-hash": "4b6bb4a29b9cbd7a05ba0cc319b1aa4c",
"packages": [ "packages": [
{ {
"name": "asm89/stack-cors", "name": "asm89/stack-cors",
@ -197,6 +197,353 @@
}, },
"time": "2021-08-13T13:06:58+00:00" "time": "2021-08-13T13:06:58+00:00"
}, },
{
"name": "doctrine/cache",
"version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "331b4d5dbaeab3827976273e9356b3b453c300ce"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce",
"reference": "331b4d5dbaeab3827976273e9356b3b453c300ce",
"shasum": ""
},
"require": {
"php": "~7.1 || ^8.0"
},
"conflict": {
"doctrine/common": ">2.2,<2.4"
},
"require-dev": {
"alcaeus/mongo-php-adapter": "^1.1",
"cache/integration-tests": "dev-master",
"doctrine/coding-standard": "^8.0",
"mongodb/mongodb": "^1.1",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"predis/predis": "~1.0",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"symfony/cache": "^4.4 || ^5.2 || ^6.0@dev",
"symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev"
},
"suggest": {
"alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
"homepage": "https://www.doctrine-project.org/projects/cache.html",
"keywords": [
"abstraction",
"apcu",
"cache",
"caching",
"couchdb",
"memcached",
"php",
"redis",
"xcache"
],
"support": {
"issues": "https://github.com/doctrine/cache/issues",
"source": "https://github.com/doctrine/cache/tree/2.1.1"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache",
"type": "tidelift"
}
],
"time": "2021-07-17T14:49:29+00:00"
},
{
"name": "doctrine/dbal",
"version": "3.3.5",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "719663b15983278227669c8595151586a2ff3327"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/719663b15983278227669c8595151586a2ff3327",
"reference": "719663b15983278227669c8595151586a2ff3327",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2",
"doctrine/cache": "^1.11|^2.0",
"doctrine/deprecations": "^0.5.3",
"doctrine/event-manager": "^1.0",
"php": "^7.3 || ^8.0",
"psr/cache": "^1|^2|^3",
"psr/log": "^1|^2|^3"
},
"require-dev": {
"doctrine/coding-standard": "9.0.0",
"jetbrains/phpstorm-stubs": "2021.1",
"phpstan/phpstan": "1.5.3",
"phpstan/phpstan-strict-rules": "^1.1",
"phpunit/phpunit": "9.5.16",
"psalm/plugin-phpunit": "0.16.1",
"squizlabs/php_codesniffer": "3.6.2",
"symfony/cache": "^5.2|^6.0",
"symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0",
"vimeo/psalm": "4.22.0"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
},
"bin": [
"bin/doctrine-dbal"
],
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\DBAL\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
}
],
"description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
"homepage": "https://www.doctrine-project.org/projects/dbal.html",
"keywords": [
"abstraction",
"database",
"db2",
"dbal",
"mariadb",
"mssql",
"mysql",
"oci8",
"oracle",
"pdo",
"pgsql",
"postgresql",
"queryobject",
"sasql",
"sql",
"sqlite",
"sqlserver",
"sqlsrv"
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/3.3.5"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal",
"type": "tidelift"
}
],
"time": "2022-04-05T09:50:18+00:00"
},
{
"name": "doctrine/deprecations",
"version": "v0.5.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/deprecations.git",
"reference": "9504165960a1f83cc1480e2be1dd0a0478561314"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314",
"reference": "9504165960a1f83cc1480e2be1dd0a0478561314",
"shasum": ""
},
"require": {
"php": "^7.1|^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^6.0|^7.0|^8.0",
"phpunit/phpunit": "^7.0|^8.0|^9.0",
"psr/log": "^1.0"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
"homepage": "https://www.doctrine-project.org/",
"support": {
"issues": "https://github.com/doctrine/deprecations/issues",
"source": "https://github.com/doctrine/deprecations/tree/v0.5.3"
},
"time": "2021-03-21T12:59:47+00:00"
},
{
"name": "doctrine/event-manager",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/event-manager.git",
"reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f",
"reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"conflict": {
"doctrine/common": "<2.9@dev"
},
"require-dev": {
"doctrine/coding-standard": "^6.0",
"phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\": "lib/Doctrine/Common"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
},
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
}
],
"description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
"homepage": "https://www.doctrine-project.org/projects/event-manager.html",
"keywords": [
"event",
"event dispatcher",
"event manager",
"event system",
"events"
],
"support": {
"issues": "https://github.com/doctrine/event-manager/issues",
"source": "https://github.com/doctrine/event-manager/tree/1.1.x"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager",
"type": "tidelift"
}
],
"time": "2020-05-29T18:28:51+00:00"
},
{ {
"name": "doctrine/inflector", "name": "doctrine/inflector",
"version": "2.0.4", "version": "2.0.4",
@ -2305,6 +2652,55 @@
], ],
"time": "2021-12-04T23:24:31+00:00" "time": "2021-12-04T23:24:31+00:00"
}, },
{
"name": "psr/cache",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/cache.git",
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Cache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for caching libraries",
"keywords": [
"cache",
"psr",
"psr-6"
],
"support": {
"source": "https://github.com/php-fig/cache/tree/3.0.0"
},
"time": "2021-02-03T23:26:27+00:00"
},
{ {
"name": "psr/container", "name": "psr/container",
"version": "2.0.2", "version": "2.0.2",

View File

@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('text_color')->default('#000000');
$table->string('background_color')->default('#FFFFFF');
$table->string('font')->default('Arial');
$table->float('font_size', 3, 1)->default(16);
$table->float('line_spacing', 2, 3)->default(1.5);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('text_color');
});
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('background_color');
});
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('font');
});
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('font_size');
});
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('line_spacing');
});
}
};

View File

@ -24,6 +24,7 @@ const sessionPassphrase = sessionStorage.getItem("key") as string;
let pages: IList[] = []; let pages: IList[] = [];
let getPageContentUrl, let getPageContentUrl,
postUrl, postUrl,
settings,
removeUrl, removeUrl,
checkword, checkword,
startDate, startDate,
@ -34,6 +35,7 @@ if (app) {
pages = JSON.parse("" + app.getAttribute('data-list')) as IList[]; pages = JSON.parse("" + app.getAttribute('data-list')) as IList[];
postUrl = "" + app.getAttribute('data-post'); postUrl = "" + app.getAttribute('data-post');
startDate = "" + app.getAttribute('data-start'); startDate = "" + app.getAttribute('data-start');
settings = JSON.parse("" + app.getAttribute('data-settings'));
removeUrl = "" + app.getAttribute('data-remove'); removeUrl = "" + app.getAttribute('data-remove');
csrf = "" + app.getAttribute('data-csrf'); csrf = "" + app.getAttribute('data-csrf');
checkword = "" + app.getAttribute('data-checkword'); checkword = "" + app.getAttribute('data-checkword');
@ -195,6 +197,7 @@ function App() {
csrf={csrf} csrf={csrf}
removeUrl={removeUrl} removeUrl={removeUrl}
loadPages={loadPages} loadPages={loadPages}
settings={settings}
setAllLoaded={setAllLoaded} setAllLoaded={setAllLoaded}
/> />
<Divider/> <Divider/>

View File

@ -5,7 +5,7 @@ import {IPages} from "../../interfaces/IPages";
import {IList} from "../../interfaces/IList"; import {IList} from "../../interfaces/IList";
import {isAllLoadedLocally} from "../../utils"; import {isAllLoadedLocally} from "../../utils";
export default function Pages({pages, url, removeUrl, csrf, passphrase, loadPages, setAllLoaded}: IPages) { export default function Pages({pages, url, removeUrl, csrf, passphrase, loadPages, settings, setAllLoaded}: IPages) {
const isPassphraseSet = passphrase !== null; const isPassphraseSet = passphrase !== null;
const perPage = 1; const perPage = 1;
const total = pages.length; const total = pages.length;
@ -24,26 +24,22 @@ export default function Pages({pages, url, removeUrl, csrf, passphrase, loadPage
const json = await response.json(); const json = await response.json();
if (json.success) { if (json.success) {
console.log(listPages.length);
const newListPages = listPages.filter(function (value) { const newListPages = listPages.filter(function (value) {
console.log(value.id);
return value.id !== id; return value.id !== id;
}); });
setListPages(newListPages); setListPages(newListPages);
console.log(newListPages.length);
console.log(listPages.length);
updateListPages(newListPages.slice((currentPage - 1) * perPage, ((currentPage - 1) * perPage) + perPage).map(page => updateListPages(newListPages.slice((currentPage - 1) * perPage, ((currentPage - 1) * perPage) + perPage).map(page =>
<Page page={page} url={url} remove={removePage} passphrase={passphrase} key={page.id}/>)); <Page page={page} url={url} remove={removePage} settings={settings} passphrase={passphrase} key={page.id}/>));
} }
} }
const [listPagesDisplayed, updateListPages] = React.useState(listPages.slice((currentPage-1) * perPage, ((currentPage-1) * perPage) + perPage ).map(page => const [listPagesDisplayed, updateListPages] = React.useState(listPages.slice((currentPage-1) * perPage, ((currentPage-1) * perPage) + perPage ).map(page =>
<Page page={page} url={url} remove={removePage} passphrase={passphrase} key={page.id} />)); <Page page={page} url={url} remove={removePage} settings={settings} passphrase={passphrase} key={page.id} />));
const handlePageChange = (event: React.ChangeEvent<unknown>, value: number) => { const handlePageChange = (event: React.ChangeEvent<unknown>, value: number) => {
setPage(value); setPage(value);
updateListPages(listPages.slice((value-1) * perPage, ((value-1) * perPage) + perPage).map(page => updateListPages(listPages.slice((value-1) * perPage, ((value-1) * perPage) + perPage).map(page =>
<Page page={page} url={url} remove={removePage} passphrase={passphrase} key={page.id}/>)); <Page page={page} url={url} settings={settings} remove={removePage} passphrase={passphrase} key={page.id}/>));
}; };
const loadAllPages = function() { const loadAllPages = function() {
@ -61,7 +57,7 @@ export default function Pages({pages, url, removeUrl, csrf, passphrase, loadPage
localStorage.setItem(page.id + "text", json.content); localStorage.setItem(page.id + "text", json.content);
updateListPages([]); updateListPages([]);
updateListPages(listPages.slice((currentPage-1) * perPage, ((currentPage-1) * perPage) + perPage).map(page => updateListPages(listPages.slice((currentPage-1) * perPage, ((currentPage-1) * perPage) + perPage).map(page =>
<Page page={page} url={url} remove={removePage} passphrase={passphrase} key={page.id}/>)); <Page page={page} url={url} remove={removePage} settings={settings} passphrase={passphrase} key={page.id}/>));
} }
} }

View File

@ -3,7 +3,7 @@ import {
AlertTitle, AlertTitle,
Card, CardContent, Card, CardContent,
CardHeader, Collapse, CardHeader, Collapse,
Grid, IconButton Grid, IconButton,
} from '@mui/material'; } from '@mui/material';
import MoreVertIcon from '@mui/icons-material/MoreVert'; import MoreVertIcon from '@mui/icons-material/MoreVert';
import * as React from 'react'; import * as React from 'react';
@ -12,7 +12,7 @@ import {Delete, Download} from "@mui/icons-material";
import MDEditor from "@uiw/react-md-editor"; import MDEditor from "@uiw/react-md-editor";
import {PropPage} from "../../interfaces/PropPage"; import {PropPage} from "../../interfaces/PropPage";
const Page = function({page, url, passphrase, remove}: PropPage) { const Page = function({page, url, passphrase, settings, remove}: PropPage) {
const [more, setMore] = React.useState<boolean>(false); const [more, setMore] = React.useState<boolean>(false);
const handleMoreClick = () => { setMore(!more); }; const handleMoreClick = () => { setMore(!more); };
@ -49,10 +49,22 @@ const Page = function({page, url, passphrase, remove}: PropPage) {
); );
} }
return ( return (
<Grid item xs={12} sm={12} md={12} id={page.id}> <Grid item xs={12} sm={12} md={12} id={page.id}
sx={{
bgcolor: settings.background_color,
color: settings.text_color,
fontSize: settings.font_size + "px",
fontFamily: settings.font,
lineHeight: settings.line_spacing
}}>
<Card> <Card>
<CardHeader <CardHeader
sx={{
bgcolor: settings.background_color,
color: settings.text_color
}}
action={ action={
icons icons
} }
@ -65,7 +77,13 @@ const Page = function({page, url, passphrase, remove}: PropPage) {
</IconButton> </IconButton>
</Collapse> </Collapse>
</CardHeader> </CardHeader>
<CardContent> <CardContent sx={{
backgroundColor: settings.background_color,
color: settings.text_color,
fontSize: settings.font_size + "px",
fontFamily: settings.font,
lineHeight: settings.line_spacing
}}>
<MDEditor.Markdown source={content} /> <MDEditor.Markdown source={content} />
</CardContent> </CardContent>
</Card> </Card>

View File

@ -13,6 +13,12 @@ export default function BasicMenu({nickname}) {
setAnchorEl(null); setAnchorEl(null);
}; };
function gotoSettings() {
const currentHref = location.href.split('/');
currentHref.pop();
location.href = currentHref.join('/') + '/settings';
}
function logout() { function logout() {
const logoutForm: HTMLFormElement = document.getElementById("logout-form") as HTMLFormElement; const logoutForm: HTMLFormElement = document.getElementById("logout-form") as HTMLFormElement;
logoutForm.submit(); logoutForm.submit();
@ -39,7 +45,7 @@ export default function BasicMenu({nickname}) {
'aria-labelledby': 'basic-button', 'aria-labelledby': 'basic-button',
}} }}
> >
{/*<MenuItem onClick={handleClose}>My account</MenuItem>*/} <MenuItem onClick={gotoSettings}>Settings</MenuItem>
<MenuItem onClick={logout}>Logout</MenuItem> <MenuItem onClick={logout}>Logout</MenuItem>
</Menu> </Menu>
</div> </div>

View File

@ -28,6 +28,12 @@ export default function MobileMenu({nickname}) {
setState( open ); setState( open );
}; };
function gotoSettings() {
const currentHref = location.href.split('/');
currentHref.pop();
location.href = currentHref.join('/') + '/settings';
}
function logout() { function logout() {
const logoutForm: HTMLFormElement = document.getElementById("logout-form") as HTMLFormElement; const logoutForm: HTMLFormElement = document.getElementById("logout-form") as HTMLFormElement;
logoutForm.submit(); logoutForm.submit();
@ -53,7 +59,7 @@ export default function MobileMenu({nickname}) {
onKeyDown={toggleDrawer(false)} onKeyDown={toggleDrawer(false)}
> >
<List> <List>
<ListItem button key={"user"}> <ListItem key={"user"}>
<ListItemIcon> <ListItemIcon>
<MailIcon /> <MailIcon />
</ListItemIcon> </ListItemIcon>
@ -62,7 +68,13 @@ export default function MobileMenu({nickname}) {
</List> </List>
<Divider /> <Divider />
<List> <List>
<ListItem button key={"user.logout"} onClick={logout}> <ListItem key={"user.settings"} onClick={gotoSettings}>
<ListItemIcon>
<MailIcon />
</ListItemIcon>
<ListItemText primary={"Settings"} />
</ListItem>
<ListItem key={"user.logout"} onClick={logout}>
<ListItemIcon> <ListItemIcon>
<InboxIcon /> <InboxIcon />
</ListItemIcon> </ListItemIcon>

View File

@ -1,5 +1,6 @@
import {IList} from "./IList"; import {IList} from "./IList";
import * as React from "react"; import * as React from "react";
import {ISettings} from "./ISettings";
export interface IPages { export interface IPages {
pages: IList[]; pages: IList[];
@ -7,6 +8,7 @@ export interface IPages {
removeUrl: string; removeUrl: string;
csrf: string; csrf: string;
passphrase: string; passphrase: string;
settings: ISettings;
loadPages: React.MutableRefObject<() => void>; loadPages: React.MutableRefObject<() => void>;
setAllLoaded: React.Dispatch<React.SetStateAction<boolean>>; setAllLoaded: React.Dispatch<React.SetStateAction<boolean>>;
} }

View File

@ -0,0 +1,7 @@
export interface ISettings {
background_color: string;
text_color: string;
font_size: number;
font: string;
line_spacing: number;
}

View File

@ -1,9 +1,11 @@
import { Ref } from "react"; import { Ref } from "react";
import {IList} from "./IList"; import {IList} from "./IList";
import {ISettings} from "./ISettings";
export interface PropPage { export interface PropPage {
page: IList; page: IList;
url: string; url: string;
settings: ISettings;
passphrase: string; passphrase: string;
remove: (id) => void; remove: (id) => void;
ref: Ref<IList>; ref: Ref<IList>;

View File

@ -17,6 +17,7 @@
data-remove="{{ route('pages.destroy', ['page' => 'replace_me']) }}" data-remove="{{ route('pages.destroy', ['page' => 'replace_me']) }}"
data-csrf="{{ csrf_token() }}" data-csrf="{{ csrf_token() }}"
data-checkword="{{ $checkword }}" data-checkword="{{ $checkword }}"
data-settings="{{ $settings }}"
> >
</div> </div>
</div> </div>

View File

@ -0,0 +1,96 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Settings') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<!-- Session Status -->
<x-auth-session-status class="mb-4" :status="session('status')" />
<!-- Validation Errors -->
<x-auth-validation-errors class="mb-4" :errors="$errors" />
<div class="p-6 bg-white border-b border-gray-200">
<form action="{{ route('user.storeSettings') }}" method="post">
@csrf
<div class="md:flex mb-6">
<div class="md:w-1/3">
<label class="block text-gray-600 font-bold md:text-left mb-3 md:mb-0 pr-4" for="text_color">
{{ __('Text color:') }}
</label>
</div>
<div class="md:w-2/3">
<input class="form-input block w-full focus:bg-white" id="text_color" name="text_color" type="color" value="{{ $settings['text_color'] }}">
</div>
</div>
<div class="md:flex mb-6">
<div class="md:w-1/3">
<label class="block text-gray-600 font-bold md:text-left mb-3 md:mb-0 pr-4" for="background_color">
{{ __('Background color:') }}
</label>
</div>
<div class="md:w-2/3">
<input class="form-input block w-full focus:bg-white" id="background_color" name="background_color" type="color" value="{{ $settings['background_color'] }}">
</div>
</div>
<div class="md:flex mb-6">
<div class="md:w-1/3">
<label class="block text-gray-600 font-bold md:text-left mb-3 md:mb-0 pr-4" for="font">
{{ __('Font:') }}
</label>
</div>
<div class="md:w-2/3">
<select class="form-select block w-full focus:bg-white" id="font" name="font">
<option value="Arial" {{ $settings['font'] === 'Arial' ? 'selected' : '' }}>Arial</option>
<option value="Pacifico" {{ $settings['font'] === 'Pacifico' ? 'selected' : '' }}>Pacifico</option>
</select>
</div>
</div>
<div class="md:flex mb-6">
<div class="md:w-1/3">
<label class="block text-gray-600 font-bold md:text-left mb-3 md:mb-0 pr-4" for="font_size">
{{ __('Font size:') }}
</label>
</div>
<div class="md:w-2/3">
<input class="form-input block w-full focus:bg-white" id="font_size" name="font_size" type="range" min="12" max="18" step="1" value="{{ $settings['font_size'] }}" oninput="this.nextElementSibling.value = this.value">
<output>{{ $settings['font_size'] }}</output>
</div>
</div>
<div class="md:flex mb-6">
<div class="md:w-1/3">
<label class="block text-gray-600 font-bold md:text-left mb-3 md:mb-0 pr-4" for="line_spacing">
{{ __('Line spacing:') }}
</label>
</div>
<div class="md:w-2/3">
<input class="form-input block w-full focus:bg-white" id="line_spacing" name="line_spacing" type="range" min="1" max="3" step="0.1" value="{{ $settings['line_spacing'] }}" oninput="this.nextElementSibling.value = this.value">
<output>{{ $settings['line_spacing'] }}</output>
</div>
</div>
<div class="md:flex md:items-center">
<div class="md:w-1/3"></div>
<div class="md:w-2/3">
<button class="shadow bg-yellow-700 hover:bg-yellow-500 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded" type="submit">
{{ __('Save preference') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -27,6 +27,8 @@ Route::resource('pages', PageController::class)
Route::get('/first', [UserController::class, 'first'])->middleware(['auth', 'auth.passphrase'])->name('user.first'); Route::get('/first', [UserController::class, 'first'])->middleware(['auth', 'auth.passphrase'])->name('user.first');
Route::post('/first', [UserController::class, 'storeFirst'])->middleware(['auth', 'auth.passphrase'])->name('user.storeFirst'); Route::post('/first', [UserController::class, 'storeFirst'])->middleware(['auth', 'auth.passphrase'])->name('user.storeFirst');
Route::get('/settings', [UserController::class, 'settings'])->middleware(['auth'])->name('user.settings');
Route::post('/settings', [UserController::class, 'storeSettings'])->middleware(['auth'])->name('user.storeSettings');
Route::get('/dashboard', function () { Route::get('/dashboard', function () {
return redirect(\route('pages.index')); return redirect(\route('pages.index'));