├── .default.env
├── .github
├── matchers
│ ├── phpcs.json
│ └── phpunit.json
└── workflows
│ └── checks.yml
├── .gitignore
├── .htaccess
├── API.md
├── LICENSE
├── MANUAL.md
├── Makefile
├── README.md
├── RENDER.md
├── UI.md
├── app
├── class
│ ├── Application.php
│ ├── Bookmark.php
│ ├── Clock.php
│ ├── Color.php
│ ├── Config.php
│ ├── Controller.php
│ ├── Controlleradmin.php
│ ├── Controllerapi.php
│ ├── Controllerapimedia.php
│ ├── Controllerapipage.php
│ ├── Controllerapiuser.php
│ ├── Controllerapiworkspace.php
│ ├── Controllerbookmark.php
│ ├── Controllerconnect.php
│ ├── Controllerhome.php
│ ├── Controllerinfo.php
│ ├── Controllermedia.php
│ ├── Controllerpage.php
│ ├── Controllerprofile.php
│ ├── Controllerrandom.php
│ ├── Controlleruser.php
│ ├── Controllerworkspace.php
│ ├── Element.php
│ ├── Elementv1.php
│ ├── Elementv2.php
│ ├── Exception
│ │ ├── Database
│ │ │ └── Notfoundexception.php
│ │ ├── Databaseexception.php
│ │ ├── Filesystemexception.php
│ │ ├── Filesystemexception
│ │ │ ├── Chmodexception.php
│ │ │ ├── Fileexception.php
│ │ │ ├── Folderexception.php
│ │ │ ├── Notfoundexception.php
│ │ │ └── Unlinkexception.php
│ │ ├── Forbiddenexception.php
│ │ └── Missingextensionexception.php
│ ├── Flywheel
│ │ ├── Formatter
│ │ │ └── JSON.php
│ │ ├── Predicate.php
│ │ ├── Query.php
│ │ └── Repository.php
│ ├── Folder.php
│ ├── Font.php
│ ├── Fs.php
│ ├── Graph.php
│ ├── Header.php
│ ├── Item.php
│ ├── Logger.php
│ ├── Matchable.php
│ ├── Media.php
│ ├── Mediaopt.php
│ ├── Mediaoptlist.php
│ ├── Model.php
│ ├── Modeladmin.php
│ ├── Modelbookmark.php
│ ├── Modelconnect.php
│ ├── Modeldb.php
│ ├── Modelhome.php
│ ├── Modelldap.php
│ ├── Modelmedia.php
│ ├── Modelpage.php
│ ├── Modeluser.php
│ ├── Opt.php
│ ├── Optcode.php
│ ├── Optlist.php
│ ├── Optmap.php
│ ├── Optrandom.php
│ ├── Page.php
│ ├── Pagev1.php
│ ├── Pagev2.php
│ ├── Quickcss.php
│ ├── Route.php
│ ├── Routes.php
│ ├── Servicefont.php
│ ├── Servicepostprocess.php
│ ├── Servicerender.php
│ ├── Servicerenderv1.php
│ ├── Servicerenderv2.php
│ ├── Servicerss.php
│ ├── Servicesession.php
│ ├── Servicetags.php
│ ├── Serviceurlchecker.php
│ ├── Summary.php
│ ├── User.php
│ └── Workspace.php
├── fn
│ └── fn.php
└── view
│ └── templates
│ ├── admin.php
│ ├── alertcommandnotfound.php
│ ├── alertexistnot.php
│ ├── alertform.php
│ ├── alertlayout.php
│ ├── alertnotpublished.php
│ ├── alertprivate.php
│ ├── alertrandom.php
│ ├── backtopbar.php
│ ├── connect.php
│ ├── delete.php
│ ├── edit.php
│ ├── edithelp.php
│ ├── editleftbar.php
│ ├── editrightbar.php
│ ├── edittabs.php
│ ├── edittopbar.php
│ ├── footer.php
│ ├── forbidden.php
│ ├── home.php
│ ├── homebookmark.php
│ ├── homemenu.php
│ ├── homeopt.php
│ ├── info.php
│ ├── layout.php
│ ├── macro_tablesort.php
│ ├── media.php
│ ├── mediamenu.php
│ ├── modallayout.php
│ ├── pagepassword.php
│ ├── profile.php
│ ├── readerlayout.php
│ ├── user.php
│ └── userconfirmdelete.php
├── assets
├── css
│ ├── admin.css
│ ├── back.css
│ ├── base.css
│ ├── connect.css
│ ├── edit.css
│ ├── fork-awesome.css
│ ├── home.css
│ ├── info.css
│ ├── media.css
│ ├── modal.css
│ ├── profile.css
│ ├── theme
│ │ ├── audrey-s-book.css
│ │ ├── blue-whale.css
│ │ ├── dark-doriphore.css
│ │ ├── default.css
│ │ ├── funky-freddy.css
│ │ ├── fuzzy-flamingo.css
│ │ ├── industrial-dream.css
│ │ └── soy-n-wasabi.css
│ └── user.css
└── fonts
│ ├── forkawesome-webfont.eot
│ ├── forkawesome-webfont.svg
│ ├── forkawesome-webfont.ttf
│ ├── forkawesome-webfont.woff
│ └── forkawesome-webfont.woff2
├── codecov.yaml
├── composer.json
├── composer.lock
├── index.php
├── package-lock.json
├── package.json
├── phpcs.xml
├── phpstan.neon
├── phpunit.xml
├── src
├── edit.js
├── fn
│ └── fn.js
├── graph.js
├── home.js
├── map.js
├── media.js
├── pagemap.js
└── sentry.js
└── tests
├── .gitattributes
├── FilesTest.php
├── LoggerTest.php
├── Servicerenderv1Test.php
├── Servicerenderv2Test.php
├── SummaryTest.php
├── data
├── Servicerenderv1Test
│ ├── body-test.html
│ ├── body-test.json
│ ├── date-time-test.html
│ ├── date-time-test.json
│ ├── empty-test.html
│ ├── empty-test.json
│ ├── external-links-test.html
│ ├── external-links-test.json
│ ├── markdown-test-2.html
│ ├── markdown-test-2.json
│ ├── markdown-test.html
│ └── markdown-test.json
└── Servicerenderv2Test
│ ├── body-test-v2.html
│ ├── body-test-v2.json
│ ├── date-time-test-v2.html
│ ├── date-time-test-v2.json
│ ├── empty-test-v2.html
│ ├── empty-test-v2.json
│ ├── external-links-test-v2.html
│ ├── external-links-test-v2.json
│ ├── markdown-test-v2.html
│ └── markdown-test-v2.json
├── fixtures
└── README.md
└── fn.php
/.default.env:
--------------------------------------------------------------------------------
1 | # .env
2 |
3 | # GitHub token used by release-it to publish releases
4 | GITHUB_TOKEN=
5 |
6 | # Sentry variables used to publish releases to Sentry
7 | SENTRY_AUTH_TOKEN=
8 | SENTRY_ORG=vincent-peugnet
9 | SENTRY_PROJECT=wcms
10 |
--------------------------------------------------------------------------------
/.github/matchers/phpcs.json:
--------------------------------------------------------------------------------
1 | {
2 | "problemMatcher": [
3 | {
4 | "owner": "phpcs",
5 | "fileLocation": "relative",
6 | "pattern": [
7 | {
8 | "regexp": "^\"(.+)\",(\\d+),(\\d+),(.+),\"(.+)\",(.+),(\\d+),(\\d+)$",
9 | "file": 1,
10 | "line": 2,
11 | "column": 3,
12 | "severity": 4,
13 | "message": 5,
14 | "code": 6
15 | }
16 | ]
17 | }
18 | ]
19 | }
--------------------------------------------------------------------------------
/.github/matchers/phpunit.json:
--------------------------------------------------------------------------------
1 | {
2 | "problemMatcher": [
3 | {
4 | "owner": "phpunit",
5 | "fileLocation": "absolute",
6 | "pattern": [
7 | {
8 | "regexp": "^\\d+\\)\\s.*$"
9 | },
10 | {
11 | "regexp": "^(.*)$",
12 | "message": 1
13 | },
14 | {
15 | "regexp": "^\\s*$"
16 | },
17 | {
18 | "regexp": "^(.*):(\\d+)$",
19 | "file": 1,
20 | "line": 2
21 | }
22 | ]
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/*
2 | .env
3 | *.log
4 | assets/atom/*
5 | assets/render/*
6 | assets/global/*
7 | assets/manual/*
8 | assets/css/tagcolors.css
9 | assets/js/*
10 | build/
11 | database/*
12 | media/*
13 | render/
14 | dist/
15 | vendor/*
16 | node_modules/
17 | config.json
18 | error_log
19 | .BUILDDATE
20 | VERSION
21 |
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 | # prevent Apache from adding trailing slash for names that match folder
2 | # in order to avoid conflict with Controllerpage::pagepermanentredirect()
3 | # and 'assets' and 'media' folder
4 | DirectorySlash off
5 | RewriteEngine on
6 | # everything that does not contain asssets|media
7 | RewriteCond %{REQUEST_URI} !^(.*)/(assets|media)/ [OR]
8 | # or that isn't a file
9 | RewriteCond %{REQUEST_FILENAME} !-f
10 | # is redirect to index
11 | RewriteRule . index.php [L]
12 |
13 |
--------------------------------------------------------------------------------
/RENDER.md:
--------------------------------------------------------------------------------
1 | W render engine scheme
2 | ======================
3 |
4 | This diagram represent W rendering chain.
5 |
6 |
7 | ```mermaid
8 | flowchart TD
9 |
10 | 0A(Head generation) -->
11 | 0rss(RSS feed declaration) --> 3B
12 |
13 | 2A[[Body]] -->
14 | 2B(W inclusion) ------->
15 | 2C((Element inclusion)) --> 2D
16 | subgraph "post inclusion parser"
17 | 2D(Summary) -->
18 | 2rss(RSS detection) -->
19 | 2H(Wiki links) -->
20 | 2I(Link and media analysis) -->
21 | 2pp(check for post render actions)
22 | end
23 | 2pp -->
24 | 3B((Head and Body gathering)) -->
25 | 3C[[Rendered HTML]] --> 4c
26 | subgraph "post render actions"
27 | 4c(counters) -->
28 | 4j(js vars)
29 | end
30 | 4j --> 5[\served web page/]
31 |
32 |
33 | 1A[[Element]] -->
34 | 1B(W inclusion) -->
35 | 1C(every link*) -->
36 | 1D(Markdown) --> 1E
37 | subgraph "post MD parser"
38 | 1E(header ID) -->
39 | 1F(URL linker) -->
40 | 1G(HTML tag*)
41 | end
42 | 1G --> 2C
43 |
44 | 1E -. "send TOC structure" .-> 2D
45 | 2rss -. "send rss links" .-> 0rss
46 | 2pp -. trigger post render action .-> 4c
47 | ```
48 |
49 | - *every link: rendering option that transform every word as a link
50 | - *HTML tag: [rendering option](MANUAL.md#html-tags) that does not print Element's corresponding HTML tags (only for pages V1)
51 |
52 |
53 |
54 |
55 | ## W inclusions
56 |
57 | List of W inclusions
58 |
59 | 1. replace `%DATE%`, `%DATEMODIF%`, `%TIME%`, `%TIMEMODIF%` codes
60 | 1. replace `%THUMBNAIL%` code
61 | 1. replace `%PAGEID%` and `%ID%` code
62 | 1. replace `%URL%` code
63 | 1. replace `%PATH%` code
64 | 1. replace `%TITLE%` code
65 | 1. replace `%DESCRIPTION%` code
66 | 1. replace `%LIST%` code
67 | 1. replace `%MEDIA%` code
68 | 1. replace `%MAP%` code
69 | 1. replace `%RANDOM%` code
70 | 1. replace `%AUTHORS%` code
71 | 1. replace `%CONNECT%` code
72 |
73 | The point of doing those inclusions early is to be before __Header ID__ parser. That way, when they are used inside HTML headings, they will generate nicer IDs.
74 |
--------------------------------------------------------------------------------
/app/class/Bookmark.php:
--------------------------------------------------------------------------------
1 | hydrateexception($datas);
39 | }
40 |
41 | public function init(
42 | string $id,
43 | string $query,
44 | string $icon = '⭐',
45 | string $name = '',
46 | string $description = ''
47 | ) {
48 | $this->setid($id);
49 | $this->setquery($query);
50 | $this->seticon($icon);
51 | $this->setname($name);
52 | $this->setdescription($description);
53 | }
54 |
55 | public function ispublic(): bool
56 | {
57 | return empty($this->user);
58 | }
59 |
60 | public function ispublished(): bool
61 | {
62 | return $this->published;
63 | }
64 |
65 |
66 | // _____________________________ G E T __________________________________
67 |
68 |
69 | public function id()
70 | {
71 | return $this->id;
72 | }
73 |
74 | public function name(): string
75 | {
76 | return $this->name;
77 | }
78 |
79 | public function description(): string
80 | {
81 | return $this->description;
82 | }
83 |
84 | public function query()
85 | {
86 | return $this->query;
87 | }
88 |
89 | public function icon()
90 | {
91 | return $this->icon;
92 | }
93 |
94 | public function user(): string
95 | {
96 | return $this->user;
97 | }
98 |
99 | public function published(): bool
100 | {
101 | return $this->published;
102 | }
103 |
104 | public function ref(): ?string
105 | {
106 | return $this->ref;
107 | }
108 |
109 | // _____________________________ S E T __________________________________
110 |
111 | public function setid($id): bool
112 | {
113 | if (is_string($id)) {
114 | $id = Model::idclean($id);
115 | if (!empty($id)) {
116 | $this->id = $id;
117 | return true;
118 | }
119 | }
120 | return false;
121 | }
122 |
123 | public function setname(string $name): bool
124 | {
125 | if (strlen($name) < self::LENGTH_SHORT_TEXT) {
126 | $this->name = strip_tags(trim($name));
127 | return true;
128 | } else {
129 | return false;
130 | }
131 | }
132 |
133 | public function setdescription(string $description): bool
134 | {
135 | if (strlen($description) < self::LENGTH_SHORT_TEXT) {
136 | $this->description = strip_tags(trim($description));
137 | return true;
138 | } else {
139 | return false;
140 | }
141 | }
142 |
143 | public function setquery($query)
144 | {
145 | if (is_string($query)) {
146 | $this->query = strip_tags(mb_substr($query, 0, Model::MAX_QUERY_LENGH));
147 | }
148 | }
149 |
150 | public function seticon($icon)
151 | {
152 | if (is_string($icon)) {
153 | $this->icon = mb_substr(strip_tags($icon), 0, 16);
154 | }
155 | }
156 |
157 | public function setuser($user)
158 | {
159 | if (is_string($user)) {
160 | $this->user = Model::idclean($user);
161 | return true;
162 | }
163 | return false;
164 | }
165 |
166 | public function setpublished(bool $published)
167 | {
168 | $this->published = $published;
169 | }
170 |
171 | /**
172 | * @param string $id ID of reference page
173 | */
174 | public function setref(string $id): void
175 | {
176 | if (!empty($id) && !Model::idcheck($id)) {
177 | $this->ref = "";
178 | }
179 | $this->ref = $id;
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/app/class/Color.php:
--------------------------------------------------------------------------------
1 | 255 || $r < 0 || $g > 255 || $g < 0 || $b > 255 || $b < 0) {
24 | throw new DomainException("Invalid rgb value: $r, $g, $b to define Color object");
25 | }
26 | $this->r = $r;
27 | $this->g = $g;
28 | $this->b = $b;
29 | }
30 |
31 | /**
32 | * @return int Luma value from 0 to 255
33 | */
34 | public function luma(): int
35 | {
36 | return ($this->r * 299 + $this->g * 587 + $this->b * 114) / 1000;
37 | }
38 |
39 | /**
40 | * @return string hexa color code starting with #
41 | */
42 | public function hexa(): string
43 | {
44 | $hex = '#';
45 | $hex .= str_pad(dechex($this->r), 2, '0', STR_PAD_LEFT);
46 | $hex .= str_pad(dechex($this->g), 2, '0', STR_PAD_LEFT);
47 | $hex .= str_pad(dechex($this->b), 2, '0', STR_PAD_LEFT);
48 | return $hex;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/class/Controlleradmin.php:
--------------------------------------------------------------------------------
1 | adminmanager = new Modeladmin();
21 |
22 | if ($this->user->isvisitor()) {
23 | http_response_code(401);
24 | $this->showtemplate('connect', ['route' => 'admin']);
25 | exit;
26 | }
27 | if (!$this->user->isadmin()) {
28 | http_response_code(403);
29 | $this->showtemplate('forbidden');
30 | exit;
31 | }
32 | }
33 |
34 | public function desktop()
35 | {
36 | $datas['pagelist'] = $this->pagemanager->list();
37 | $this->mediamanager = new Modelmedia();
38 | $datas['faviconlist'] = $this->mediamanager->listfavicon();
39 | $datas['thumbnaillist'] = $this->mediamanager->listthumbnail();
40 | $datas['themes'] = $this->mediamanager->listthemes();
41 |
42 | $globalcssfile = Model::GLOBAL_CSS_FILE;
43 |
44 | if (is_file($globalcssfile)) {
45 | $datas['globalcss'] = file_get_contents($globalcssfile);
46 | } else {
47 | $datas['globalcss'] = "";
48 | }
49 |
50 | try {
51 | $datas['pagetables'] = $this->adminmanager->pagetables();
52 | } catch (RuntimeException $e) {
53 | Logger::errorex($e);
54 | $datas['pagetables'] = [];
55 | }
56 |
57 | $this->showtemplate('admin', $datas);
58 | }
59 |
60 | public function update()
61 | {
62 | try {
63 | Fs::accessfile(Model::GLOBAL_CSS_FILE, true);
64 | Fs::writefile(Model::GLOBAL_CSS_FILE, $_POST['globalcss'], 0664);
65 | Config::hydrate($_POST);
66 | Config::savejson();
67 | $this->sendflashmessage("Configuration succesfully updated", self::FLASH_SUCCESS);
68 | } catch (Filesystemexception $e) {
69 | $this->sendflashmessage("Can't write config file or global css file", self::FLASH_ERROR);
70 | }
71 | $this->routedirect('admin');
72 | }
73 |
74 | public function database()
75 | {
76 | if (!empty($_POST['action'])) {
77 | switch ($_POST['action']) {
78 | case 'duplicate':
79 | if (!empty($_POST['dbsrc']) && !empty($_POST['dbtarget'])) {
80 | $this->adminmanager->copydb($_POST['dbsrc'], $_POST['dbtarget']);
81 | }
82 | break;
83 | case 'select':
84 | if (!empty($_POST['pagetable'])) {
85 | Config::hydrate($_POST);
86 | try {
87 | $this->pagemanager->flushrendercache();
88 | Config::savejson();
89 | } catch (RuntimeException $e) {
90 | $this->sendflashmessage(
91 | 'Cannot update Config file : ' . $e->getMessage(),
92 | self::FLASH_ERROR
93 | );
94 | }
95 | }
96 | break;
97 | }
98 | }
99 | $this->routedirect('admin');
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/class/Controllerapi.php:
--------------------------------------------------------------------------------
1 | getrequestbody();
46 | try {
47 | $datas = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
48 | } catch (JsonException $e) {
49 | $this->shortresponse(400, "Json decoding error: " . $e->getMessage());
50 | }
51 | return $datas;
52 | }
53 |
54 | /**
55 | * Response containing a HTTP header code and a message encoded in JSON
56 | *
57 | * @param int $code HTTP response code header
58 | * @param string $message Error message to display
59 | */
60 | protected function shortresponse(int $code, string $message = ""): never
61 | {
62 | http_response_code($code);
63 | header('Content-type: application/json; charset=utf-8');
64 | echo json_encode(["message" => $message], JSON_PRETTY_PRINT);
65 | exit;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/class/Controllerapimedia.php:
--------------------------------------------------------------------------------
1 | mediamanager = new Modelmedia();
18 | }
19 |
20 | /**
21 | * Upload a single file to a target directory. Folders are created automatically.
22 | * It will erase any already existing file.
23 | */
24 | public function upload(string $path): void
25 | {
26 | if (!$this->user->iseditor()) {
27 | $this->shortresponse(403, 'Unauthorized to upload files');
28 | }
29 | try {
30 | $file = $this->getrequestbody();
31 | } catch (Error $e) {
32 | $this->shortresponse(400, 'Error while reading the stream: ' . $e->getMessage());
33 | }
34 | $path = rawurldecode($path);
35 | $pathinfo = pathinfo($path);
36 | $dirname = Model::MEDIA_DIR . $pathinfo['dirname']; //without trailing slash
37 | try {
38 | Fs::dircheck($dirname, true, 0775);
39 | Fs::writefile(Model::MEDIA_DIR . $path, $file, 0664);
40 | $this->shortresponse(200, 'File successfully uploaded');
41 | } catch (Filesystemexception $e) {
42 | $this->shortresponse(400, 'Error while saving file: ' . $e->getMessage());
43 | }
44 | }
45 |
46 | public function delete(string $path): void
47 | {
48 | if (!$this->user->iseditor()) {
49 | $this->shortresponse(403, 'Unauthorized to upload files');
50 | }
51 | try {
52 | $media = new Media(Model::MEDIA_DIR . $path);
53 | $this->mediamanager->delete($media);
54 | } catch (RuntimeException $e) {
55 | $this->shortresponse(400, 'Error while deleting media file: ' . $e->getMessage());
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/class/Controllerapiuser.php:
--------------------------------------------------------------------------------
1 | user->isadmin()) {
18 | $this->shortresponse(401, 'Access unauthrozed, you need to be admin');
19 | }
20 | try {
21 | $user = $this->usermanager->get($user);
22 | http_response_code(200);
23 | header('Content-type: application/json; charset=utf-8');
24 | echo json_encode($user, JSON_PRETTY_PRINT);
25 | } catch (Notfoundexception $e) {
26 | $this->shortresponse(404, 'User not found');
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/class/Controllerapiworkspace.php:
--------------------------------------------------------------------------------
1 | workspace->hydrate($_POST);
11 | $this->servicesession->setworkspace($this->workspace);
12 | } else {
13 | $this->shortresponse(400, "No POST datas recieved");
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/class/Controllerinfo.php:
--------------------------------------------------------------------------------
1 | user->isvisitor()) {
14 | http_response_code(401);
15 | $this->showtemplate('connect', ['route' => 'info']);
16 | exit;
17 | }
18 | }
19 |
20 | public function desktop()
21 | {
22 | if ($this->user->isinvite()) {
23 | $version = getversion();
24 | $mandir = Model::MAN_RENDER_DIR;
25 | try {
26 | $manual = Fs::readfile("$mandir/manual_$version.html");
27 | $summary = Fs::readfile("$mandir/summary_$version.html");
28 | } catch (RuntimeException $e) {
29 | try {
30 | $mansrc = Fs::readfile(Model::MAN_FILE);
31 | $render = new Servicerenderv2($this->router, $this->pagemanager, true);
32 | $manual = $render->rendermanual($mansrc);
33 |
34 | $sum = new Summary(['min' => 2, 'max' => 4, 'sum' => $render->sum()]);
35 | $summary = $sum->sumparser();
36 |
37 | Fs::folderflush($mandir);
38 | Fs::dircheck($mandir, true, 0775);
39 |
40 | Fs::writefile("$mandir/manual_$version.html", $manual);
41 | Fs::writefile("$mandir/summary_$version.html", $summary);
42 | } catch (RuntimeException $e) {
43 | $manual = '⚠️ Error while trying to access MANUAL.md file.';
44 | $summary = '';
45 | }
46 | }
47 | $this->showtemplate('info', ['version' => getversion(), 'manual' => $manual, 'summary' => $summary]);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/class/Controllerprofile.php:
--------------------------------------------------------------------------------
1 | user->isvisitor()) {
16 | http_response_code(401);
17 | $this->showtemplate('connect', ['route' => 'profile']);
18 | exit;
19 | }
20 | }
21 |
22 | public function desktop()
23 | {
24 | try {
25 | $datas['user'] = $this->usermanager->get($this->user);
26 | $this->showtemplate('profile', $datas);
27 | } catch (Notfoundexception $e) {
28 | $this->sendflashmessage($e->getMessage(), self::FLASH_ERROR);
29 | $this->routedirect('home');
30 | }
31 | }
32 |
33 | public function update()
34 | {
35 | try {
36 | $user = $this->usermanager->get($this->user);
37 | $user->hydrateexception($_POST);
38 | $this->usermanager->update($user);
39 | $this->sendflashmessage('Successfully updated', self::FLASH_SUCCESS);
40 | } catch (Notfoundexception $e) {
41 | $this->sendflashmessage($e->getMessage(), self::FLASH_ERROR);
42 | } catch (RuntimeException $e) {
43 | $this->sendflashmessage(
44 | 'There was a problem when updating preference : ' . $e->getMessage(),
45 | self::FLASH_ERROR
46 | );
47 | }
48 | $this->routedirect('profile');
49 | }
50 |
51 | /**
52 | * Update the user's password.
53 | */
54 | public function password()
55 | {
56 | if ($this->user->isldap()) {
57 | http_response_code(403);
58 | $this->showtemplate('forbidden', ['route' => 'profile']);
59 | exit;
60 | }
61 |
62 | if (
63 | !isset($_POST['currentpassword']) ||
64 | !$this->usermanager->passwordcheck($this->user, $_POST['currentpassword'])
65 | ) {
66 | $this->sendflashmessage("wrong current password", self::FLASH_ERROR);
67 | $this->routedirect('profile');
68 | }
69 |
70 | if (
71 | empty($_POST['password1']) ||
72 | empty($_POST['password2']) ||
73 | $_POST['password1'] !== $_POST['password2']
74 | ) {
75 | $this->sendflashmessage("passwords does not match", self::FLASH_ERROR);
76 | $this->routedirect('profile');
77 | }
78 |
79 | if (
80 | !$this->user->setpassword($_POST['password1']) ||
81 | !$this->user->hashpassword()
82 | ) {
83 | $this->sendflashmessage("password is not compatible", self::FLASH_ERROR);
84 | $this->routedirect('profile');
85 | }
86 |
87 | try {
88 | $this->usermanager->add($this->user);
89 | $this->sendflashmessage('password updated successfully', self::FLASH_SUCCESS);
90 | } catch (Databaseexception $e) {
91 | $this->sendflashmessage($e->getMessage(), self::FLASH_ERROR);
92 | }
93 | $this->routedirect('profile');
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/class/Controllerrandom.php:
--------------------------------------------------------------------------------
1 | optrandom = new Optrandom($_GET);
14 |
15 | try {
16 | $origin = $this->pagemanager->get($this->optrandom->origin());
17 |
18 | $pages = $this->pagemanager->pagelist();
19 | $pages = $this->pagemanager->pagetable($pages, $this->optrandom);
20 | unset($pages[$origin->id()]);
21 | $keys = array_intersect_key($pages, array_flip($origin->linkto()));
22 | if (!empty($keys)) {
23 | $page = $pages[array_rand($keys)];
24 | $this->routedirect('pageread', ['page' => $page->id()]);
25 | } else {
26 | $message = 'Empty set of page';
27 | }
28 | } catch (RuntimeException $e) {
29 | $message = 'Origin page does not exist';
30 | }
31 |
32 | if (isset($message)) {
33 | $this->showtemplate('alertrandom', ['message' => $message]);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/class/Controllerworkspace.php:
--------------------------------------------------------------------------------
1 | user->isinvite()) {
10 | $this->workspace->hydrate($_POST);
11 | $this->servicesession->setworkspace($this->workspace);
12 | }
13 |
14 | switch ($_POST['route']) {
15 | case 'pageedit':
16 | if (isset($_POST['page'])) {
17 | $this->routedirect('pageedit', ['page' => $_POST['page']]);
18 | }
19 | break;
20 |
21 | case 'home':
22 | $this->routedirect('home');
23 | break;
24 |
25 | case 'media':
26 | $this->routedirect('media');
27 | break;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/class/Element.php:
--------------------------------------------------------------------------------
1 | fullmatch;
43 | }
44 |
45 | public function options(): string
46 | {
47 | return $this->options;
48 | }
49 |
50 | public function everylink(): int
51 | {
52 | return $this->everylink;
53 | }
54 |
55 | public function markdown(): bool
56 | {
57 | return $this->markdown;
58 | }
59 |
60 | public function content(): string
61 | {
62 | return $this->content;
63 | }
64 |
65 | public function minheaderid(): int
66 | {
67 | return $this->minheaderid;
68 | }
69 |
70 | public function maxheaderid(): int
71 | {
72 | return $this->maxheaderid;
73 | }
74 |
75 | public function headerid(): string
76 | {
77 | return $this->headerid;
78 | }
79 |
80 | public function headeranchor(): int
81 | {
82 | return $this->headeranchor;
83 | }
84 |
85 | public function urllinker(): bool
86 | {
87 | return $this->urllinker;
88 | }
89 |
90 |
91 |
92 |
93 |
94 |
95 | // ______________________________________________ S E T ________________________________________________________
96 |
97 |
98 | public function setfullmatch(string $fullmatch)
99 | {
100 | $this->fullmatch = $fullmatch;
101 | }
102 |
103 | public function setoptions(string $options)
104 | {
105 | if (!empty($options)) {
106 | $this->options = $options;
107 | }
108 | }
109 |
110 | public function seteverylink(int $level)
111 | {
112 | if ($level >= 0 && $level <= 16) {
113 | $this->everylink = $level;
114 | return true;
115 | } else {
116 | return false;
117 | }
118 | }
119 |
120 | public function setmarkdown($markdown)
121 | {
122 | $this->markdown = boolval($markdown);
123 | }
124 |
125 | public function setcontent(string $content)
126 | {
127 | $this->content = $content;
128 | }
129 |
130 | public function setheaderid(string $headerid)
131 | {
132 | if ($headerid == 0) {
133 | $this->headerid = 0;
134 | } else {
135 | preg_match('~([1-6])\-([1-6])~', $headerid, $out);
136 | $this->minheaderid = intval($out[1]);
137 | $this->maxheaderid = intval($out[2]);
138 | }
139 | }
140 |
141 | public function setheaderanchor($headeranchor)
142 | {
143 | if (in_array($headeranchor, self::HEADER_ANCHOR_MODES)) {
144 | $this->headeranchor = (int) $headeranchor;
145 | }
146 | }
147 |
148 | public function seturllinker($urllinker)
149 | {
150 | $this->urllinker = boolval($urllinker);
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/app/class/Elementv1.php:
--------------------------------------------------------------------------------
1 | tag = Config::htmltag();
19 | $this->urllinker = Config::urllinker();
20 | $this->fullmatch = $fullmatch;
21 | $type = strtolower($type);
22 | if (in_array($type, Pagev1::HTML_ELEMENTS)) {
23 | $this->type = $type;
24 | } else {
25 | throw new DomainException("$type is not a valid Page HTML Element Type");
26 | }
27 | $this->options = $options;
28 | $this->analyse($pageid);
29 | }
30 |
31 | protected function analyse(string $pageid)
32 | {
33 | if (!empty($this->options)) {
34 | $this->options = str_replace('*', $pageid, $this->options);
35 | parse_str($this->options, $datas);
36 | if (isset($datas['id'])) {
37 | $this->sources = explode(' ', $datas['id']);
38 | } else {
39 | $this->sources = [$pageid];
40 | }
41 | $this->hydrate($datas);
42 | } else {
43 | $this->sources = [$pageid];
44 | }
45 | }
46 |
47 |
48 | // ______________________________________________ G E T ________________________________________________________
49 |
50 | public function sources(): array
51 | {
52 | return $this->sources;
53 | }
54 |
55 | public function type(): string
56 | {
57 | return $this->type;
58 | }
59 |
60 | public function tag(): bool
61 | {
62 | return $this->tag;
63 | }
64 |
65 | public function settag($tag)
66 | {
67 | $this->tag = boolval($tag);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/class/Elementv2.php:
--------------------------------------------------------------------------------
1 | urllinker = Config::urllinker();
12 | $this->fullmatch = $fullmatch;
13 | $this->id = $pageid;
14 | $this->options = $options;
15 | $this->analyse($pageid);
16 | }
17 |
18 | protected function analyse(string $pageid)
19 | {
20 | parse_str($this->options, $datas);
21 | $this->hydrate($datas);
22 | }
23 |
24 |
25 |
26 | // ______________________________________________ G E T ________________________________________________________
27 |
28 | public function id(): string
29 | {
30 | return $this->id;
31 | }
32 |
33 | public function setid($id): void
34 | {
35 | $this->id = $id;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/class/Exception/Database/Notfoundexception.php:
--------------------------------------------------------------------------------
1 | operators = array(
10 | '>', '>=', '<', '<=', '==', '===', '!=', '!==', 'IN'
11 | );
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/class/Flywheel/Query.php:
--------------------------------------------------------------------------------
1 | predicate = new Predicate();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/class/Flywheel/Repository.php:
--------------------------------------------------------------------------------
1 | path, Model::FOLDER_PERMISSION)) {
18 | throw new RuntimeException("error while trying to change permission of database folder: " . $this->path);
19 | }
20 | }
21 |
22 | /**
23 | * Get an array containing the path of all files in this repository
24 | *
25 | * @return array An array, item is a file
26 | */
27 | public function getAllFiles()
28 | {
29 | $ext = $this->formatter->getFileExtension();
30 | $files = glob($this->path . DIRECTORY_SEPARATOR . '*.' . $ext);
31 | return $files;
32 | }
33 |
34 | /**
35 | * Get an array containing the id of all files in this repository
36 | *
37 | * @return array An array, item is a id
38 | */
39 | public function getAllIds()
40 | {
41 | $ext = $this->formatter->getFileExtension();
42 | return array_map(function ($path) use ($ext) {
43 | return $this->getIdFromPath($path, $ext);
44 | }, $this->getAllFiles());
45 | }
46 |
47 | protected function write($path, $contents): bool
48 | {
49 | $ret = parent::write($path, $contents);
50 | chmod($path, Model::FILE_PERMISSION);
51 | return $ret;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/class/Folder.php:
--------------------------------------------------------------------------------
1 | name = $name;
19 | $this->childs = $childs;
20 | $this->filecount = $filecount;
21 | $this->path = $path;
22 | $this->deepness = $deepness;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/class/Graph.php:
--------------------------------------------------------------------------------
1 | 'cose',
14 | 'fcose' => 'fcose',
15 | 'cose-bilkent' => 'cose-bilkent',
16 | 'euler' => 'euler',
17 | 'circle' => 'circle',
18 | 'breadthfirst' => 'breadthfirst',
19 | 'concentric' => 'concentric',
20 | 'grid' => 'grid',
21 | 'random' => 'random',
22 | ];
23 |
24 | /**
25 | * @param mixed[] $datas
26 | */
27 | public function __construct(array $datas)
28 | {
29 | $this->hydrate($datas);
30 | }
31 |
32 | public function showorphans(): bool
33 | {
34 | return $this->showorphans;
35 | }
36 |
37 | public function showredirection(): bool
38 | {
39 | return $this->showredirection;
40 | }
41 |
42 | public function showexternallinks(): bool
43 | {
44 | return $this->showexternallinks;
45 | }
46 |
47 | public function layout(): string
48 | {
49 | return $this->layout;
50 | }
51 |
52 | public function setshowredirection($showredirection): void
53 | {
54 | $this->showredirection = boolval($showredirection);
55 | }
56 |
57 | public function setshoworphans($showorphans): void
58 | {
59 | $this->showorphans = boolval($showorphans);
60 | }
61 |
62 | public function setshowexternallinks($showexternallinks): void
63 | {
64 | $this->showexternallinks = boolval($showexternallinks);
65 | }
66 |
67 | public function setlayout($layout): void
68 | {
69 | if (key_exists($layout, $this::LAYOUTS)) {
70 | $this->layout = $layout;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/class/Header.php:
--------------------------------------------------------------------------------
1 | id = $id;
22 | $this->level = $level;
23 | $this->title = $title;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/class/Logger.php:
--------------------------------------------------------------------------------
1 | getMessage()} in {$e->getFile()}({$e->getLine()})";
64 | }
65 |
66 | /**
67 | * Log an error message using printf format.
68 | */
69 | public static function error(string $msg, ...$args)
70 | {
71 | if (self::$verbosity > 0) {
72 | self::write('ERROR', $msg, $args);
73 | }
74 | }
75 |
76 | /**
77 | * Log a warning message using printf format.
78 | */
79 | public static function warning(string $msg, ...$args)
80 | {
81 | if (self::$verbosity > 1) {
82 | self::write('WARN', $msg, $args);
83 | }
84 | }
85 |
86 | /**
87 | * Log an info message using printf format.
88 | */
89 | public static function info(string $msg, ...$args)
90 | {
91 | if (self::$verbosity > 2) {
92 | self::write('INFO', $msg, $args);
93 | }
94 | }
95 |
96 | /**
97 | * Log a debug message using printf format.
98 | */
99 | public static function debug(string $msg, ...$args)
100 | {
101 | if (self::$verbosity > 3) {
102 | self::write('DEBUG', $msg, $args);
103 | }
104 | }
105 |
106 | /**
107 | * Log an exception as an error.
108 | */
109 | public static function errorex(Throwable $e, bool $withtrace = false)
110 | {
111 | if (self::$verbosity > 0) {
112 | $msg = self::exceptionmessage($e);
113 | if ($withtrace) {
114 | // TODO: Maybe print a more beautiful stack trace.
115 | $msg .= PHP_EOL . $e->getTraceAsString();
116 | }
117 | self::write('ERROR', $msg);
118 | }
119 | }
120 |
121 | /**
122 | * Log an exception as a warning.
123 | */
124 | public static function warningex(Throwable $e)
125 | {
126 | if (self::$verbosity > 1) {
127 | self::write('WARN', self::exceptionmessage($e));
128 | }
129 | }
130 |
131 | /**
132 | * Log an exception as an info.
133 | */
134 | public static function infoex(Throwable $e)
135 | {
136 | if (self::$verbosity > 2) {
137 | self::write('INFO', self::exceptionmessage($e));
138 | }
139 | }
140 |
141 | /**
142 | * Log an exception as a debug.
143 | */
144 | public static function debugex(Throwable $e)
145 | {
146 | if (self::$verbosity > 3) {
147 | self::write('DEBUG', self::exceptionmessage($e));
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/app/class/Matchable.php:
--------------------------------------------------------------------------------
1 | path = "/" . rtrim(Model::MEDIA_DIR, "/");
28 | $this->type = Media::mediatypes();
29 | $this->hydrate($datas);
30 | }
31 |
32 | /**
33 | * Generate link address for table header
34 | *
35 | * @param string $sortby
36 | * @return string link address
37 | */
38 | public function getsortbyaddress(string $sortby): string
39 | {
40 | if (!in_array($sortby, Modelmedia::MEDIA_SORTBY)) {
41 | $sortby = 'id';
42 | }
43 | if ($this->sortby === $sortby) {
44 | $order = $this->order * -1;
45 | } else {
46 | $order = $this->order;
47 | }
48 | $query = ['path' => $this->path, 'sortby' => $sortby, 'order' => $order];
49 | if (array_diff(Media::mediatypes(), $this->type) != []) {
50 | $query['type'] = $this->type;
51 | }
52 | return '?' . urldecode(http_build_query($query));
53 | }
54 |
55 | /**
56 | * Give the GET params to be used for redirection. Using hidden input under the `route` name.
57 | *
58 | * @param string $path Media path to display. Default is the current path.
59 | * @return string URL-encoded path, filter and sort parameters, startiting with a `?`
60 | */
61 | public function getpathaddress(string $path = null): string
62 | {
63 | $path = is_null($path) ? $this->path : "/$path";
64 | $query = ['path' => $path, 'sortby' => $this->sortby, 'order' => $this->order];
65 | if (array_diff(Media::mediatypes(), $this->type) != []) {
66 | $query['type'] = $this->type;
67 | }
68 | return '?' . urldecode(http_build_query($query));
69 | }
70 |
71 |
72 | // ___________________ MAGIC FOLDERS _____________________
73 |
74 |
75 | public function isfontdir(): bool
76 | {
77 | return $this->dir() === Model::FONT_DIR;
78 | }
79 |
80 | public function iscssdir(): bool
81 | {
82 | return $this->dir() === Model::CSS_DIR;
83 | }
84 |
85 | public function isthumbnaildir(): bool
86 | {
87 | return $this->dir() === Model::THUMBNAIL_DIR;
88 | }
89 |
90 | public function isfavicondir(): bool
91 | {
92 | return $this->dir() === Model::FAVICON_DIR;
93 | }
94 |
95 | // ______________________________________________ G E T ________________________________________________________
96 |
97 |
98 | /**
99 | * @return string formated like `/media/
6 | 🛑 Command 👁️ read page
13 | 💡 You may want to try:
14 | ';
51 | } elseif ($media->type() == 'sound') {
52 | $div .= '
';
72 | }
73 | if ($header->level <= $prevlevel) {
74 | $sumstring .= '/= $this->e($command) ?>
not found
7 | = $this->e($id) ?>
8 | insert('alertform', ['id' => $page->id()]) ?> 9 |
10 | 11 | 12 | 13 | 14 | 15 | iseditor()) : ?> 16 |17 | ⭐ Create 18 |
19 | 20 |
21 | 💡 To create a page in one command, you can type
22 | = $this->upage('pageadd', $page->id()) ?>
23 | directly in your address bar.
24 |
27 | 🏠 Go back to home 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | stop() ?> 39 | 40 | -------------------------------------------------------------------------------- /app/view/templates/alertform.php: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /app/view/templates/alertlayout.php: -------------------------------------------------------------------------------- 1 | layout('readerlayout') ?> 2 | 3 | start('head') ?> 4 | 5 | = Wcms\Config::alertcss() ? '' : '' ?> 6 | 7 | 8 | 9 | 10 | stop() ?> 11 | 12 | 13 | 14 | start('page') ?> 15 | 16 | 29 | 30 | 31 |9 | insert('alertform', ['id' => $page->id()]) ?> 10 |
11 | 12 | 13 | 14 | stop() ?> 15 | 16 | -------------------------------------------------------------------------------- /app/view/templates/alertprivate.php: -------------------------------------------------------------------------------- 1 | layout('alertlayout', ['subtitle' => $subtitle]) ?> 2 | 3 | start('alert') ?> 4 | 5 | 6 | isvisitor() && Wcms\Config::privatepass()) : ?> 7 | 8 |9 | insert('alertform', ['id' => $page->id()]) ?> 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | stop() ?> 18 | 19 | -------------------------------------------------------------------------------- /app/view/templates/alertrandom.php: -------------------------------------------------------------------------------- 1 | layout('alertlayout', ['subtitle' => '']) ?> 2 | 3 | 4 | 5 | 6 | start('alert') ?> 7 | 8 | 9 | = !empty(Wcms\Config::alerttitle()) ? '= $message ?>
14 | 15 | stop() ?> 16 | -------------------------------------------------------------------------------- /app/view/templates/backtopbar.php: -------------------------------------------------------------------------------- 1 |URL : = $this->upage('pageread', $page->id()) ?>
7 |Id : = $page->id() ?>
8 |Title : = $this->e($page->title()) ?>
9 |Number of edits : = $page->editcount() ?>
10 |Number of displays : = $page->displaycount() ?>
11 |12 | Page linking to this one : = $pageslinkingtocount ?> 13 | 0) : ?> 14 | 15 | 16 | 17 | 18 |
19 | 20 | 37 | 38 | stop() ?> 39 | -------------------------------------------------------------------------------- /app/view/templates/edit.php: -------------------------------------------------------------------------------- 1 | layout('layout', ['title' => '✏ ' . $this->e($page->title()), 'stylesheets' => [ 3 | Wcms\Model::jspath() . 'edit.bundle.css', 4 | $css . 'edit.css', 5 | $css . 'tagcolors.css' 6 | ], 'favicon' => $page->favicon()]) 7 | ?> 8 | 9 | start('page') ?> 10 | 11 | 12 | 13 | insert('backtopbar', ['user' => $user, 'tab' => 'edit', 'pagelist' => $pagelist, 'pageid' => $page->id()]) ?> 14 | insert('edittopbar', ['page' => $page, 'user' => $user, 'workspace' => $workspace, 'target' => $target]) ?> 15 | 16 |[hello](PAGE_ID/URL)
link
img<e@mail.net>
# h1 title
## h2 title
*emphasis*
**strong**
- list item
> blockquote
code
------
horizontal line[[page_id]]
wiki link%TITLE%
print page title%DESCRIPTION%
print page description%THUMBNAIL%
print page thumbnail%DATE%
print date of page%TIME%
print time of page%SUMMARY?option=value%
generate summary%LIST?option=value%
generate list of page%MEDIA?option=value%
generate media list%ELEMENT?option=value%
include specified element59 | BODY don't support Markdown encoding. 60 |
61 | 62 |8 | Sorry = $this->e($user->name()) ?>, you are not allowed to do this. 9 |
10 | 11 | 12 | 13 | 14 | 15 |Go back
16 |
17 |
18 | stop() ?>
19 |
--------------------------------------------------------------------------------
/app/view/templates/homebookmark.php:
--------------------------------------------------------------------------------
1 |
47 |
--------------------------------------------------------------------------------
/app/view/templates/info.php:
--------------------------------------------------------------------------------
1 | layout('layout', ['title' => 'Documentation', 'stylesheets' => [$css . 'back.css', $css . 'info.css']]) ?>
2 |
3 | start('page') ?>
4 |
5 | insert('backtopbar', ['user' => $user, 'tab' => 'info', 'pagelist' => $pagelist]) ?>
6 |
7 | Documentation
29 |
30 |
id | 18 |= $user->id() ?> | 19 |
connection counter | 22 |= $user->connectcount() ?> | 23 |
account expiration | 26 |= $user->expiredate('hrdi') ?> | 27 |
Id : = $userdelete->id() ?>
10 |Level : = $userdelete->level() ?>
11 | 12 | 17 | 18 | abort 19 | 20 | 21 | 22 |You can't delete yourself!
23 |To delete this user, create at least another admin user, log in as this other admin user, then try to delete this user.
24 | 25 | 26 | 27 | 28 | 29 | stop() ?> 30 | -------------------------------------------------------------------------------- /assets/css/admin.css: -------------------------------------------------------------------------------- 1 | /* ADMIN */ 2 | 3 | -------------------------------------------------------------------------------- /assets/css/back.css: -------------------------------------------------------------------------------- 1 | /* BACK */ 2 | /* Used everywhere except in Edit view */ 3 | 4 | /* --------------------------------------------------------- content 5 | 6 | content 7 | main layout 8 | filters 9 | footer 10 | grid layout 11 | .info icon before paragraphs 12 | icon 13 | media queries 14 | 15 | /* --------------------------------------------------------- variables */ 16 | 17 | .scroll { 18 | overflow: auto; 19 | height: 100%; 20 | max-width: 100%; 21 | } 22 | 23 | .code, code { 24 | display: block; 25 | white-space: nowrap; 26 | color: var(--code-color) !important; 27 | background-color: var(--code-background-color); 28 | padding: 2px; 29 | font-family: monospace; 30 | font-size: 15px; 31 | width: 100%; 32 | border: none; 33 | overflow: hidden; 34 | } 35 | 36 | 37 | /* Display mode links */ 38 | 39 | h2 a { 40 | color: var(--outline-background-color); 41 | } 42 | 43 | h2 a.selected, h2 a:hover { 44 | color: var(--text2-color); 45 | } 46 | 47 | 48 | /* --------------------------------------------------------- main layout */ 49 | 50 | main section { 51 | display: flex; 52 | flex-direction: column; 53 | flex: 1; 54 | background: var(--primary-background-color); 55 | gap: var(--gap, 0); 56 | flex-wrap: nowrap; 57 | width: 100%; 58 | overflow: hidden; 59 | } 60 | 61 | 62 | /* --------------------------------------------------------- filters */ 63 | /* #filter is common to Home and Media */ 64 | 65 | #filter fieldset { 66 | padding: 0; 67 | border: none; 68 | margin: 0; 69 | } 70 | 71 | #filter fieldset[data-default="0"] legend::before { 72 | content: "~ "; 73 | display: inline; 74 | position: absolute; 75 | width: 1em; 76 | left: var(--spacing); 77 | text-indent: 0; 78 | } 79 | 80 | #filter fieldset[data-default="0"] legend { 81 | text-indent: 1em; 82 | position: relative; 83 | } 84 | 85 | #filter legend .help { 86 | text-indent: 0; 87 | } 88 | 89 | #filter input[type="submit"] { 90 | width: 100%; 91 | } 92 | 93 | /* --------------------------------------------------------- footer */ 94 | 95 | footer { 96 | background-color: black; 97 | color: white; 98 | opacity: 0.4; 99 | } 100 | 101 | 102 | /* --------------------------------------------------------- grid layout */ 103 | 104 | .grid { 105 | display: grid; 106 | grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); 107 | overflow: auto; 108 | } 109 | .grid-item { 110 | background: var(--primary-background-color); 111 | padding: var(--double-spacing); 112 | display: flex; 113 | flex-direction: column; 114 | gap: var(--spacing); 115 | } 116 | .grid-item form { 117 | display: flex; 118 | flex-direction: column; 119 | gap: var(--spacing); 120 | } 121 | .grid-item a { 122 | text-decoration: underline; 123 | } 124 | .grid-item textarea { 125 | width: 100%; 126 | } 127 | .grid-item h2 { 128 | margin: calc(-1 * var(--double-spacing)) calc(-1 * var(--double-spacing)) 0 calc(-1 * var(--double-spacing)); 129 | } 130 | .grid-item h3 { 131 | margin: 1.2em 0 0; 132 | font-size: 1em; 133 | padding: var(--spacing) 0 var(--half-spacing); 134 | border-bottom: 1px solid var(--outline-background-color); 135 | 136 | } 137 | 138 | 139 | /* --------------------------------------------------------- .info icon before paragraphs */ 140 | p.info { 141 | line-height: 1.4; 142 | margin: 1.5em 0 var(--spacing); 143 | } 144 | p.info::before { 145 | content: "i"; 146 | font-family: 'Courier New', Courier, monospace; 147 | background: var(--outline-color); 148 | color: var(--outline-background-color) !important; 149 | border-radius: 100%; 150 | position: relative; 151 | top: -.25em; 152 | display: inline-flex; 153 | width: 1.2em; 154 | height: 1.2em; 155 | font-size: var(--size-small); 156 | justify-content: center; 157 | align-items: center; 158 | margin-right: 1ch; 159 | } 160 | 161 | 162 | /* --------------------------------------------------------- icon */ 163 | 164 | img.icon { 165 | height: 12px; 166 | } 167 | 168 | a:hover img.icon { 169 | filter: invert(1); 170 | } 171 | 172 | 173 | /* --------------------------------------------------------- media queries */ 174 | 175 | @media (max-width: 550px) { 176 | main { 177 | flex-direction: column; 178 | overflow-y: auto; 179 | } 180 | 181 | main > * { 182 | width: 100%; 183 | max-width: inherit !important; 184 | } 185 | 186 | } 187 | 188 | @media (pointer: coarse) { 189 | aside summary { 190 | padding-bottom: 5px; 191 | padding-top: 5px; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /assets/css/connect.css: -------------------------------------------------------------------------------- 1 | /* CONNECT */ 2 | 3 | .connect form { 4 | display: flex; 5 | flex-direction: column; 6 | gap: var(--spacing); 7 | } 8 | 9 | .connect h2 { 10 | padding: var(--spacing); 11 | border-radius: var(--radius) var(--radius) 0 0; 12 | } 13 | 14 | .connect { 15 | max-width: 250px; 16 | display: flex; 17 | flex-direction: column; 18 | gap: var(--spacing); 19 | margin: auto; 20 | border-radius: var(--radius); 21 | padding: var(--spacing); 22 | background: var(--secondary-background-color); 23 | } 24 | 25 | .button { 26 | display: block; 27 | width: 100%; 28 | text-align: center; 29 | } -------------------------------------------------------------------------------- /assets/css/home.css: -------------------------------------------------------------------------------- 1 | /* HOME */ 2 | 3 | 4 | /* --------------------------------------------------------- bookmarks */ 5 | 6 | .bookmark { 7 | display: flex; 8 | gap: var(--spacing); 9 | align-items: center; 10 | } 11 | 12 | a.bookmark { 13 | display: block; 14 | white-space: nowrap; 15 | max-width: 10em; 16 | overflow: hidden; 17 | text-overflow: ellipsis; 18 | } 19 | 20 | /* currently selected bookmark */ 21 | a.bookmark[data-current="1"] { 22 | background-color: var(--outline-background-color); 23 | color: var(--outline-color); 24 | padding: var(--half-spacing) var(--spacing); 25 | border-radius: var(--radius); 26 | } 27 | 28 | 29 | /* --------------------------------------------------------- filters */ 30 | 31 | /* for tags and authors, manage space between label and counter */ 32 | .field .label-with-counter { 33 | display: flex; 34 | } 35 | 36 | .field .label-with-counter .label { 37 | flex:1; 38 | text-overflow: ellipsis; 39 | overflow: hidden; 40 | } 41 | 42 | .field .label-with-counter .counter { 43 | border-radius: 100%; 44 | width: 1.2em; 45 | height: 1.2em; 46 | display: flex; 47 | justify-content: center; 48 | align-items: center; 49 | font-size: var(--size-small); 50 | } 51 | 52 | fieldset.tag input[type="radio"], fieldset.authors input[type="radio"] { 53 | /* make input/label closer on authors and tags radios */ 54 | margin-right: -2px; 55 | } 56 | 57 | td label, td time, 58 | td.tag, td.description { 59 | white-space: nowrap; 60 | } 61 | 62 | details#display input[type="color"] { 63 | /* limit color input height widthin Display dropdown */ 64 | height: 1.5em; 65 | } 66 | 67 | 68 | /* --------------------------------------------------------- deep search */ 69 | 70 | div#deepsearchbar { 71 | background-color: var(--secondary-background-color); 72 | padding: var(--spacing); 73 | } 74 | 75 | #deepsearchbar input[type=text] { 76 | width: 150px; 77 | } 78 | 79 | #deepsearchbar details, #deepsearchbar summary { 80 | display: inline; 81 | cursor: pointer; 82 | } 83 | 84 | /* --------------------------------------------------------- list view */ 85 | 86 | main table td.id label { 87 | font-family: monospace; 88 | font-size: var(--size-small); 89 | } 90 | 91 | #home2table a.linkto { 92 | font-family: monospace; 93 | font-size: var(--size-small); 94 | background-color: var(--main-color); 95 | color: var(--text2-color); 96 | text-wrap: nowrap; 97 | padding: 0 var(--padding); 98 | border-radius: var(--radius); 99 | } 100 | 101 | table td a.tag, table a.author { 102 | border-radius: 10px; 103 | padding: 1px 4px; 104 | } 105 | 106 | table td a.author { 107 | background-color: var(--button-background-color); 108 | color: var(--button-color); 109 | } 110 | 111 | table td a.secure{ 112 | padding: 1px 3px; 113 | color: black; 114 | } 115 | 116 | table a.secure.private { 117 | background-color: #b9b67b; 118 | } 119 | 120 | table a.secure.not_published { 121 | background-color: #b97b7b; 122 | } 123 | 124 | table td a.secure.public { 125 | background-color: #80b97b; 126 | } 127 | 128 | table .favicon img { 129 | height: 16px; 130 | max-width: 32px; 131 | } 132 | 133 | table .deadlinkcount, table .uncheckedlinkcount { 134 | border-radius: 15px; 135 | display: inline-block; 136 | height: 17px; 137 | width: 17px; 138 | text-align: center; 139 | color: white; 140 | } 141 | 142 | table .deadlinkcount { 143 | background-color: red; 144 | } 145 | 146 | table .uncheckedlinkcount { 147 | background-color: rgb(65, 65, 65); 148 | } 149 | 150 | td.title { 151 | max-width: 150px; 152 | overflow: hidden; 153 | text-overflow: ellipsis; 154 | } 155 | 156 | td.date, td.datemodif, td.datecreation { 157 | text-wrap: nowrap; 158 | } 159 | 160 | 161 | /* --------------------------------------------------------- graph view */ 162 | 163 | main div#graph { 164 | height: 100%; 165 | width: 100%; 166 | } 167 | 168 | 169 | /* --------------------------------------------------------- map view */ 170 | 171 | #map, 172 | #geomap { 173 | width: 100%; 174 | height: 100%; 175 | } 176 | #map p { 177 | padding: var(--spacing); 178 | } 179 | .leaflet-control-container { 180 | position: absolute; 181 | } 182 | 183 | 184 | /* --------------------------------------------------------- media queries */ 185 | 186 | @media (pointer: coarse) { 187 | 188 | aside #save-workspace { 189 | display: none; 190 | } 191 | a.bookmark .icon { 192 | font-size: 120%; 193 | } 194 | td.edit a, td.read a, td.delete a, td.download a { 195 | margin: 0 5px; 196 | } 197 | #deepsearchbar summary { 198 | font-size: 22px; 199 | padding: 0 10px; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /assets/css/info.css: -------------------------------------------------------------------------------- 1 | /* INFO — DOCUMENTATION */ 2 | 3 | 4 | /* --------------------------------------------------------- manual */ 5 | 6 | #manual { 7 | hyphens: auto; 8 | font-size: 1.1em; 9 | max-width: 60em; 10 | user-select: text; 11 | margin: auto; 12 | padding: var(--double-spacing); 13 | } 14 | #manual a { 15 | text-decoration: underline; 16 | text-underline-offset: .15em; 17 | color: currentColor; 18 | } 19 | 20 | #manual p { 21 | line-height: 1.4em; 22 | margin: 1em 0; 23 | } 24 | #manual li { 25 | margin: 5px 0; 26 | line-height: 23px; 27 | } 28 | 29 | #manual a[href^="https://"]::after { 30 | content: " ◥"; 31 | color: var(--main-color); 32 | } 33 | 34 | #manual a[href^="#"]::after { 35 | content: " ✻"; 36 | color: var(--main-color); 37 | white-space: nowrap; 38 | } 39 | 40 | #manual i { 41 | font-style: normal; 42 | color: #7b97b9; 43 | } 44 | 45 | #manual h1 { 46 | display: none; 47 | } 48 | 49 | #manual h2 { 50 | font-size: 40px; 51 | margin-block: 2em .5em; 52 | font-size: clamp(3em, 3vw, 6em); 53 | background: none; 54 | color: var(--text3-color); 55 | padding: 0; 56 | scroll-margin-top: 1em; 57 | } 58 | 59 | #manual h3 { 60 | background-color: var(--main-color); 61 | margin: 2em 0 1em; 62 | padding: var(--spacing); 63 | font-size: 35px; 64 | width: auto; 65 | border-width: 2px; 66 | border-radius: var(--radius); 67 | background: var(--secondary-background-color); 68 | display: inline-block; 69 | scroll-margin-top: 1em; 70 | } 71 | 72 | #manual h4 { 73 | border-bottom: solid 2px var(--main-color); 74 | font-size: x-large; 75 | margin: 1em 0; 76 | scroll-margin-top: 1em; 77 | } 78 | 79 | #manual h5 { 80 | text-transform: uppercase; 81 | margin-top: 50px; 82 | margin-bottom: 0; 83 | margin-left: 0; 84 | font-size: medium; 85 | border: solid 1px; 86 | width: fit-content; 87 | padding: 0 3px; 88 | border-left: 8px solid; 89 | border-color: var(--main-color); 90 | } 91 | 92 | #manual table { 93 | margin-top: 12px; 94 | } 95 | #manual th { 96 | background-color: unset; 97 | border-bottom: 1px solid; 98 | } 99 | 100 | #manual th, 101 | #manual td { 102 | padding: var(--spacing) 0; 103 | } 104 | 105 | #manual ul { 106 | list-style: inside disc; 107 | padding-inline-start: 10px; 108 | } 109 | 110 | #manual pre { 111 | padding: var(--half-spacing) var(--spacing); 112 | background-color: var(--code-color); 113 | white-space: pre-wrap; 114 | border-radius: var(--radius); 115 | line-height: 1.4; 116 | } 117 | 118 | #manual kbd { 119 | background-color: var(--main-color); 120 | color: var(--text2-color); 121 | padding: 1px 4px; 122 | border-radius: 7px; 123 | box-shadow: 2px 2px; 124 | margin: 0 2px; 125 | } 126 | #manual code { 127 | width: fit-content; 128 | font-family: monospace; 129 | white-space: pre-wrap; 130 | display: inline; 131 | padding: var(--half-spacing); 132 | border-radius: var(--radius); 133 | background-color: var(--code-color); 134 | color: var(--code-background-color) !important; 135 | } 136 | 137 | #manual pre code { 138 | padding: 0; 139 | } 140 | 141 | #manual blockquote { 142 | margin: 30px 0; 143 | margin-left: 15px; 144 | padding-left: 5px; 145 | border-left: solid 3px var(--main-color); 146 | } 147 | 148 | 149 | /* --------------------------------------------------------- media queries */ 150 | 151 | @media (max-width: 750px) { 152 | #manual { 153 | font-size: 1em; 154 | } 155 | } 156 | 157 | 158 | @media (max-width: 550px) { 159 | main { 160 | display: block; 161 | overflow: scroll; 162 | } 163 | } 164 | 165 | 166 | /* --------------------------------------------------------- toc */ 167 | 168 | #toc { 169 | overflow: hidden; /* allow scroll */ 170 | min-width: 180px; 171 | background: var(--primary-background-color); 172 | padding-bottom: 2em; 173 | } 174 | 175 | #toc .scroll > :not(h2){ 176 | padding: var(--double-spacing); 177 | } 178 | 179 | .summary { 180 | padding: var(--spacing); 181 | line-height: 1.4; 182 | } 183 | 184 | .summary li a { 185 | text-decoration: none; 186 | } 187 | 188 | .summary li a:hover { 189 | text-decoration: underline; 190 | } 191 | 192 | .summary > li { 193 | margin-bottom: 10px; 194 | } 195 | 196 | .summary > li > a { 197 | font-weight: bold; 198 | font-size: 1.15em; 199 | } 200 | 201 | .summary > li > ul > li > ul { 202 | font-size: var(--size-small); 203 | border-left: solid 2px var(--main-color); 204 | padding-left: 5px; 205 | margin-left: 5px; 206 | margin-block: 6px; 207 | opacity: 0.9; 208 | } 209 | 210 | .summary > li > ul { 211 | padding-left: 15px; 212 | } 213 | -------------------------------------------------------------------------------- /assets/css/modal.css: -------------------------------------------------------------------------------- 1 | /* MODAL */ 2 | 3 | .modal form { 4 | display: flex; 5 | flex-direction: column; 6 | gap: var(--spacing); 7 | } 8 | 9 | .modal h2 { 10 | padding: var(--spacing); 11 | border-radius: var(--radius) var(--radius) 0 0; 12 | } 13 | 14 | .modal { 15 | max-width: 500px; 16 | display: flex; 17 | flex-direction: column; 18 | gap: var(--spacing); 19 | margin: auto; 20 | border-radius: var(--radius); 21 | padding: var(--spacing); 22 | background: var(--secondary-background-color); 23 | } 24 | 25 | .button { 26 | display: block; 27 | width: 100%; 28 | text-align: center; 29 | } 30 | -------------------------------------------------------------------------------- /assets/css/profile.css: -------------------------------------------------------------------------------- 1 | /* PROFILE */ 2 | -------------------------------------------------------------------------------- /assets/css/theme/audrey-s-book.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --text-color: #272f09; 3 | --text2-color: #595d1e; 4 | --text3-color: #3b1338; 5 | --main-color: #eaeeaf; 6 | --secondary-background-color: #d2bd76; 7 | --code-background-color: hsl(213 21% 10% / 1); 8 | --code-color: hsl(89.26deg 46% 66%); 9 | --primary-background-color: #ffefbc; 10 | --tertiary-background-color: #c6b98e; 11 | --outline-background-color: #e7c969; 12 | --outline-color: #262626; 13 | --button-background-color: #dcdd98; 14 | --button-color: #000000; 15 | --input-background-color: #ffffff; 16 | --input-color: #303030; 17 | color-scheme: light; 18 | 19 | --radius: 4px; 20 | } 21 | 22 | .cm-wcms, .cm-wkeyword, .editor textarea, .CodeMirror { 23 | color: var(--text-color); 24 | background-color: var(--main-color); 25 | border-radius: 0; 26 | } 27 | 28 | 29 | .CodeMirror, .editor #editmain, .editor #editheader, .editor #editnav, .editor #editaside, .editor #editfooter { 30 | font-family: serif; 31 | } 32 | -------------------------------------------------------------------------------- /assets/css/theme/blue-whale.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --text-color: #dfe0eb; 3 | --text2-color: #ffffff; 4 | --text3-color: #3b1338; 5 | --main-color: #033879; 6 | --secondary-background-color: #4050bd; 7 | --code-background-color: hsl(213deg 74.31% 13.58%); 8 | --code-color: hsl(213deg 69.63% 74.23%); 9 | --primary-background-color: #5476d9; 10 | --tertiary-background-color: #061456; 11 | --outline-background-color: #1927cf; 12 | --outline-color: #ffffff; 13 | --button-background-color: #0057d9; 14 | --button-color: #ffffff; 15 | --input-background-color: #002152; 16 | --input-color: #ffffff; 17 | color-scheme: dark; 18 | 19 | --radius: 1em; 20 | } 21 | 22 | button, input[type="submit"], input[type="text"] { 23 | border: solid 1px var(--text-color); 24 | } 25 | 26 | 27 | .submenu, .block, nav.bar { 28 | border-radius: 0 0 10px 10px; 29 | } 30 | -------------------------------------------------------------------------------- /assets/css/theme/dark-doriphore.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --text-color: #ffffff; 3 | --text2-color: #ffeb01; 4 | --text3-color: #ffe001; 5 | --main-color: #4c0780; 6 | --secondary-background-color: #000000; 7 | --code-background-color: rgb(255 128 19); 8 | --code-color: #000000; 9 | --primary-background-color: #414141; 10 | --tertiary-background-color: #000000; 11 | --outline-background-color: #ffb101; 12 | --outline-color: #000000; 13 | --button-background-color: #ff662c; 14 | --button-color: #000000; 15 | --input-background-color: #000000; 16 | --input-color: #ff662c; 17 | color-scheme: dark; 18 | --font-family: monospace; 19 | --radius: 0; 20 | } 21 | 22 | input, button, select, textarea, .dropdown, .dropdown-content { 23 | border-radius: 0px; 24 | border: solid 1px; 25 | } 26 | .dropdown-content { 27 | border: solid 1px; 28 | margin-left: -1px; 29 | padding-top: 3px; 30 | } 31 | 32 | body { 33 | font-family: monospace; 34 | font-size: 13px; 35 | } 36 | 37 | aside { 38 | border-bottom: solid 1px; 39 | } 40 | -------------------------------------------------------------------------------- /assets/css/theme/default.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --text-color: black; 3 | --text2-color: #ffffff; 4 | --text3-color: #3b1338; 5 | --main-color: #7b97b9; 6 | --secondary-background-color: #bbbbbb; 7 | --code-background-color: hsl(213 21% 10% / 1); 8 | --code-color: hsl(213deg 100% 80.86%); 9 | --primary-background-color: #d6d6d6; 10 | --tertiary-background-color: #909090; 11 | --outline-background-color: #7a7a7a; 12 | --outline-color: #ffffff; 13 | --button-background-color: #ecf0f5; 14 | --button-color: #000000; 15 | --input-background-color: #ffffff; 16 | --input-color: #303030; 17 | color-scheme: light; 18 | } 19 | 20 | :root { 21 | --radius: 3px; 22 | --spacing: 6px; 23 | --padding: 3px; 24 | } -------------------------------------------------------------------------------- /assets/css/theme/funky-freddy.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --text-color: #5e0063; 3 | --text2-color: #9f075d; 4 | --text3-color: #70360e; 5 | --main-color: #10f6ba; 6 | --secondary-background-color: #ff9090; 7 | --code-background-color: rgb(75 15 83); 8 | --code-color: #f8ff00; 9 | --primary-background-color: #cdcdcd; 10 | --tertiary-background-color: #8e98bc; 11 | --outline-background-color: #119b77; 12 | --outline-color: #fff; 13 | --button-background-color: #e9e9e9; 14 | --button-color: #000000; 15 | --input-background-color: #ffffff; 16 | --input-color: #303030; 17 | color-scheme: light; 18 | 19 | --radius: 5px 20 | } 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /assets/css/theme/fuzzy-flamingo.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --text-color: black; 3 | --text2-color: #ffffff; 4 | --text3-color: #3b1338; 5 | --main-color: #fb21d6; 6 | --secondary-background-color: #e589d5; 7 | --code-background-color: rgb(17 10 54); 8 | --code-color: #e381fe; 9 | --primary-background-color: #ecbbe3; 10 | --tertiary-background-color: #fd88ff; 11 | --outline-background-color: #2e1530; 12 | --outline-color: #ffffff; 13 | --button-background-color: #b707c9; 14 | --button-color: #ffffff; 15 | --input-background-color: #eab7f0; 16 | --input-color: #9621a4; 17 | color-scheme: light; 18 | } 19 | 20 | input, button, select, textarea { 21 | border-radius: 5px; 22 | border: solid 1px var(--secondary-background-color); 23 | } 24 | 25 | 26 | 27 | input[type="submit"], button { 28 | border: none; 29 | } 30 | 31 | 32 | 33 | .block { 34 | box-shadow: -4px 8px 20px #ff00c69c; 35 | border-radius: 5px; 36 | } 37 | 38 | main > * { 39 | padding: 5px; 40 | } 41 | -------------------------------------------------------------------------------- /assets/css/theme/industrial-dream.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --text-color: black; 3 | --text2-color: #ffffff; 4 | --text3-color: #3b1338; 5 | --main-color: #848484; 6 | --secondary-background-color: #bbbbbb; 7 | --code-background-color: hsl(213 21% 10% / 1); 8 | --code-color: hsl(0deg 0% 100%); 9 | --primary-background-color: #d6d6d6; 10 | --tertiary-background-color: #909090; 11 | --outline-background-color: #5e5e5e; 12 | --outline-color: #ffffff; 13 | --button-background-color: #ecf0f5; 14 | --button-color: #000000; 15 | --input-background-color: #ffffff; 16 | --input-color: #303030; 17 | color-scheme: light; 18 | 19 | --radius: 0; 20 | } 21 | 22 | input, textarea { 23 | border: inset 2px; 24 | border-color: #333 #666 #666 #333; 25 | } 26 | 27 | input[type="submit"], button, select { 28 | border: outset 2px; 29 | } 30 | 31 | .block { 32 | border: ridge 2px; 33 | margin: 0px; 34 | } 35 | 36 | 37 | 38 | 39 | .submenu { 40 | border: ridge 2px; 41 | } 42 | 43 | h2, header { 44 | border-bottom: #757575 solid 1px; 45 | } 46 | 47 | div.panel details { 48 | border: solid 1px; 49 | border-color: var(--tertiary-background-color) 50 | } 51 | -------------------------------------------------------------------------------- /assets/css/theme/soy-n-wasabi.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --text-color: black; 3 | --text2-color: #e8e8e8; 4 | --text3-color: #13173b; 5 | --main-color: rgb(38, 39, 43); 6 | --secondary-background-color: #a5c12b; 7 | --code-background-color: hsl(213 21% 10% / 1); 8 | --code-color: hsl(0deg 0% 100%); 9 | --primary-background-color: #d6d6d6; 10 | --tertiary-background-color: #909090; 11 | --outline-background-color: #527028; 12 | --outline-color: #ffffff; 13 | --button-background-color: #ecf0f5; 14 | --button-color: #0f0f1b; 15 | --input-background-color: #ffffff; 16 | --input-color: #303030; 17 | color-scheme: light; 18 | 19 | --spacing: .5rem; 20 | --radius: .25rem; 21 | --font-family: "CommitMono", "Fira Mono", monospace; 22 | --gap: 1px; 23 | } 24 | -------------------------------------------------------------------------------- /assets/css/user.css: -------------------------------------------------------------------------------- 1 | /* USER */ 2 | 3 | 4 | main.user { 5 | display: block; 6 | overflow: scroll; 7 | } 8 | .flexrow { 9 | padding: var(--spacing); 10 | align-items: end; 11 | } 12 | 13 | table { 14 | background-color: var(--primary-background-color); 15 | } 16 | 17 | .new-user { 18 | /* minimum height for new user section */ 19 | flex: 0; 20 | } 21 | 22 | .submit-field { 23 | /* minimum width for new user submit button */ 24 | flex: 0; 25 | } 26 | 27 | /* --------------------------------------------------------- media queries */ 28 | 29 | @media (max-width: 750px) { 30 | #manual { 31 | font-size: 1em; 32 | } 33 | } 34 | 35 | @media (max-width:700px) { 36 | /* inverse direction for new user form */ 37 | .flexrow { 38 | flex-direction: column; 39 | align-items: start; 40 | justify-content: stretch; 41 | } 42 | } 43 | 44 | @media (max-width: 550px) { 45 | /* quick n dirty barely ussable table */ 46 | th { 47 | display: none; 48 | } 49 | tr { 50 | display: flex; 51 | flex-direction: column; 52 | border-bottom: 3px solid; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /assets/fonts/forkawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-peugnet/wcms/4a9d723bbcc35b64a68e5d410315f11b321fafb0/assets/fonts/forkawesome-webfont.eot -------------------------------------------------------------------------------- /assets/fonts/forkawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-peugnet/wcms/4a9d723bbcc35b64a68e5d410315f11b321fafb0/assets/fonts/forkawesome-webfont.ttf -------------------------------------------------------------------------------- /assets/fonts/forkawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-peugnet/wcms/4a9d723bbcc35b64a68e5d410315f11b321fafb0/assets/fonts/forkawesome-webfont.woff -------------------------------------------------------------------------------- /assets/fonts/forkawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-peugnet/wcms/4a9d723bbcc35b64a68e5d410315f11b321fafb0/assets/fonts/forkawesome-webfont.woff2 -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: "reach, diff" 3 | branches: # branch names that can post comment 4 | - "master" -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "w-cms/w-cms", 3 | "description": "point'n think", 4 | "license": "AGPL-3.0-or-later", 5 | "require": { 6 | "php": ">=7.4.0", 7 | "altorouter/altorouter": "^1.2", 8 | "firebase/php-jwt": "^5.2", 9 | "jamesmoss/flywheel": "^0.5.2", 10 | "league/plates": "^3.3", 11 | "michelf/php-markdown": "^1.8", 12 | "vstelmakh/url-highlight": "^3.0" 13 | }, 14 | "require-dev": { 15 | "filp/whoops": "^2.7", 16 | "pepakriz/phpstan-exception-rules": "^0.12", 17 | "phpstan/phpstan": "^1.0", 18 | "phpstan/phpstan-phpunit": "^1.0", 19 | "phpunit/phpunit": "^9.0", 20 | "sentry/sdk": "^3.1", 21 | "squizlabs/php_codesniffer": "^3.5" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Wcms\\": "app/class" 26 | }, 27 | "files": ["app/fn/fn.php"] 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Wcms\\Tests\\": "tests" 32 | }, 33 | "files": ["tests/fn.php"] 34 | }, 35 | "config": { 36 | "platform": { 37 | "php": "7.4" 38 | }, 39 | "sort-packages": true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | getMessage()); 10 | } 11 | 12 | $app = new Wcms\Application(); 13 | $app->wakeup(); 14 | 15 | session_set_cookie_params([ 16 | 'path' => '/' . Wcms\Config::basepath(), 17 | 'samesite' => 'Strict', 18 | 'secure' => Wcms\Config::issecure() 19 | ]); 20 | session_start(); 21 | 22 | if (class_exists('Whoops\Run') && !empty(Wcms\Config::debug())) { 23 | $whoops = new \Whoops\Run(); 24 | $handler = new \Whoops\Handler\PrettyPageHandler(); 25 | $handler->setEditor(\Wcms\Config::debug()); 26 | $whoops->pushHandler($handler); 27 | $whoops->register(); 28 | } 29 | 30 | if (isreportingerrors()) { 31 | Sentry\init([ 32 | 'dsn' => Wcms\Config::sentrydsn(), 33 | 'release' => getversion(), 34 | ]); 35 | Sentry\configureScope(function ($scope) { 36 | $scope->setUser([ 37 | 'id' => Wcms\Config::url(), 38 | 'username' => Wcms\Config::basepath(), 39 | ]); 40 | }); 41 | } 42 | 43 | try { 44 | $matchoper = new Wcms\Routes(); 45 | $matchoper->match(); 46 | } catch (Throwable $e) { 47 | if (isreportingerrors()) { 48 | Sentry\captureException($e); 49 | } 50 | Wcms\Logger::errorex($e, true); 51 | http_response_code(500); 52 | if (isset($whoops)) { 53 | $whoops->handleException($e); 54 | } 55 | echo '` . $e->getMessage() . '
'; 57 | echo 'Please contact yout Wiki admin to solve this.
'; 58 | } 59 | Wcms\Logger::close(); 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wcms", 3 | "repository": "github:vincent-peugnet/wcms", 4 | "license": "MIT", 5 | "dependencies": { 6 | "@yaireo/tagify": "^4.21.1", 7 | "codemirror": "^5.63.3", 8 | "cytoscape": "^3.20.0", 9 | "cytoscape-cose-bilkent": "^4.1.0", 10 | "cytoscape-euler": "^1.2.3", 11 | "cytoscape-fcose": "^2.2.0", 12 | "leaflet": "^1.9.3" 13 | }, 14 | "devDependencies": { 15 | "@sentry/browser": "^7.119.1", 16 | "@sentry/cli": "^2.8.1", 17 | "esbuild": "^0.20.0", 18 | "prettier": "^1.19.1", 19 | "semver": "^7.3.8" 20 | }, 21 | "prettier": { 22 | "tabWidth": 4, 23 | "trailingComma": "es5", 24 | "singleQuote": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 |
11 |
12 | Content of HEADER element should not be printed
13 |
14 |
15 |
16 | Titles should be transformet to links
54 | 55 |Dû tëxte encodé àveç dès châräctères nôn ASCII
62 | 63 |イリノイ州シカゴにて、アイルランド系の家庭に、9
64 | 65 |\n \n Content of HEADER element should not be printed\n \n<\/code>\n\n
\n\n
\n\n%NAV?markdown=0%\n\n
\n\n%ASIDE?falsething=0%\n\n\n%MAIN?headeranchor=1%\n\n
\n\n%FOOTER?headerid=2-5%\n\n
\n\nThis should not work<\/h1>\n\n%HEADER %\n\n%FDS%",
14 | "header": "This line should never exist in HTML render !!!!",
15 | "main": "_______________________\n\nTitles should be transformet to links\n\n# my first title\n\n## my second title\n\n## This is another title again and again, but this one is loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooonnnnnnnnger with lot of words\n\nDû tëxte encodé àveç dès châräctères nôn ASCII\n\nイリノイ州シカゴにて、アイルランド系の家庭に、9",
16 | "nav": "This `Nav` Element should have markdown disabled\n
\n
\n## This is not a title\n
\n
\n[this will stay as a text and will not become a link](not-an url)",
17 | "aside": "```\n\tAside should apear normally with markdown and everything\n```\n\n%HEADER% >>>> should not call `HEADER` Element\n",
18 | "footer": "# heading 01\n\nshould have no ID\n\n## heading 02\n\nshould have an ID\n\n### heading 03\n\nshould have an ID\n\n#### heading 04\n\nshould have an ID\n\n##### heading 05\n\nshould have an ID\n\n###### heading 06\n\nshould have no ID",
19 | "externalcss": [],
20 | "customhead": "",
21 | "secure": 0,
22 | "interface": "main",
23 | "linkto": [],
24 | "templatebody": "",
25 | "templatecss": "",
26 | "templatejavascript": "",
27 | "templateoptions": [
28 | "thumbnail",
29 | "recursivecss",
30 | "externalcss",
31 | "favicon",
32 | "externaljavascript"
33 | ],
34 | "favicon": "",
35 | "thumbnail": "",
36 | "authors": [
37 | "vincent"
38 | ],
39 | "displaycount": 16,
40 | "visitcount": 0,
41 | "editcount": 48,
42 | "sleep": 0,
43 | "redirection": "",
44 | "refresh": 0,
45 | "password": null,
46 | "version": 1
47 | }
48 |
--------------------------------------------------------------------------------
/tests/data/Servicerenderv1Test/date-time-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/tests/data/Servicerenderv1Test/date-time-test.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "date-time-test",
3 | "title": "date-time-test",
4 | "description": "",
5 | "lang": "",
6 | "tag": [],
7 | "date": "2022-10-13T19:39:55+0200",
8 | "datecreation": "2022-10-13T19:39:55+0200",
9 | "datemodif": "2022-10-13T19:39:55+0200",
10 | "daterender": "2022-10-13T19:39:55+0200",
11 | "css": "",
12 | "javascript": "",
13 | "body": "%HEADER%\n\n%NAV%\n\n%ASIDE%\n\n%MAIN%\n\n%FOOTER%",
14 | "header": "",
15 | "main": "%DATE%\n%TIME%\n%DATE?format=full&lang=fr%\n%TIME?format=medium&lang=ja%",
16 | "nav": "last modification: %DATEMODIF% at %TIMEMODIF%",
17 | "aside": "",
18 | "footer": "",
19 | "externalcss": [],
20 | "customhead": "",
21 | "secure": 0,
22 | "interface": "main",
23 | "linkto": [],
24 | "templatebody": "",
25 | "templatecss": "",
26 | "templatejavascript": "",
27 | "templateoptions": [
28 | "externalcss",
29 | "externaljavascript",
30 | "favicon",
31 | "thumbnail",
32 | "recursivecss"
33 | ],
34 | "favicon": "",
35 | "thumbnail": "",
36 | "authors": [
37 | "vincent"
38 | ],
39 | "displaycount": 0,
40 | "visitcount": 0,
41 | "editcount": 0,
42 | "sleep": 0,
43 | "redirection": "",
44 | "refresh": 0,
45 | "password": null,
46 | "version": 1
47 | }
48 |
--------------------------------------------------------------------------------
/tests/data/Servicerenderv1Test/empty-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/tests/data/Servicerenderv1Test/empty-test.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "empty-test",
3 | "title": "empty-test",
4 | "description": "",
5 | "lang": "",
6 | "tag": [],
7 | "date": "2022-10-13T19:39:55+0200",
8 | "datecreation": "2022-10-13T19:39:55+0200",
9 | "datemodif": "2022-10-13T19:39:55+0200",
10 | "daterender": "2022-10-13T19:39:55+0200",
11 | "css": "",
12 | "javascript": "",
13 | "body": "%HEADER%\n\n%NAV%\n\n%ASIDE%\n\n%MAIN%\n\n%FOOTER%",
14 | "header": "",
15 | "main": "",
16 | "nav": "",
17 | "aside": "",
18 | "footer": "",
19 | "externalcss": [],
20 | "customhead": "",
21 | "secure": 0,
22 | "interface": "main",
23 | "linkto": [],
24 | "templatebody": "",
25 | "templatecss": "",
26 | "templatejavascript": "",
27 | "templateoptions": [
28 | "externalcss",
29 | "externaljavascript",
30 | "favicon",
31 | "thumbnail",
32 | "recursivecss"
33 | ],
34 | "favicon": "",
35 | "thumbnail": "",
36 | "authors": [
37 | "vincent"
38 | ],
39 | "displaycount": 0,
40 | "visitcount": 0,
41 | "editcount": 0,
42 | "sleep": 0,
43 | "redirection": "",
44 | "refresh": 0,
45 | "password": null,
46 | "version": 1
47 | }
48 |
--------------------------------------------------------------------------------
/tests/data/Servicerenderv1Test/external-links-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | link to my external life using Michel Fortin's Markdown extra
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
38 |
39 | <a href="https://club1.fr" class="total shell">dans un codeblock</a>
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/tests/data/Servicerenderv1Test/external-links-test.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "external-links-test",
3 | "title": "external-links-test",
4 | "description": "",
5 | "lang": "",
6 | "tag": [],
7 | "date": "2022-12-08T20:17:00+0100",
8 | "datecreation": "2022-12-08T20:17:55+0100",
9 | "datemodif": "2022-12-08T23:58:25+0100",
10 | "daterender": "2022-12-08T23:58:30+0100",
11 | "css": "",
12 | "javascript": "",
13 | "body": "%HEADER%\n\n%NAV%\n\n%ASIDE%\n\n%MAIN%\n\n%FOOTER%",
14 | "header": "",
15 | "main": "\n\n[link to my external life using Michel Fortin's Markdown extra](https:\/\/club1.fr) {.testclass#testid}\n\nhttps:\/\/club1.fr\n\ntest de lien vers CLUB1<\/a>\n\ntest de lien vers CLUB1<\/a>\n\n
15 |
16 | <a href="https://club1.fr" class="total shell">dans un codeblock</a>
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/data/Servicerenderv2Test/external-links-test-v2.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "external-links-test-v2",
3 | "title": "external-links-test-v2",
4 | "description": "",
5 | "lang": "",
6 | "tag": [],
7 | "latitude": null,
8 | "longitude": null,
9 | "date": "2023-08-06T19:33:00+0200",
10 | "datecreation": "2023-08-06T19:33:34+0200",
11 | "datemodif": "2023-08-06T19:33:37+0200",
12 | "daterender": "2023-08-06T19:33:38+0200",
13 | "css": "",
14 | "javascript": "",
15 | "body": "%CONTENT%",
16 | "externalcss": [],
17 | "customhead": "",
18 | "secure": 0,
19 | "interface": "content",
20 | "linkto": [],
21 | "templatebody": "",
22 | "templatecss": "",
23 | "templatejavascript": "",
24 | "templateoptions": [
25 | "thumbnail",
26 | "recursivecss",
27 | "externalcss",
28 | "favicon",
29 | "externaljavascript"
30 | ],
31 | "favicon": "",
32 | "thumbnail": "",
33 | "authors": [
34 | "vincent"
35 | ],
36 | "displaycount": 1,
37 | "visitcount": 0,
38 | "editcount": 1,
39 | "sleep": 0,
40 | "redirection": "",
41 | "refresh": 0,
42 | "password": null,
43 | "postprocessaction": false,
44 | "version": 2,
45 | "content": "\r\n\r\n[link to my external life using Michel Fortin's Markdown extra](https:\/\/club1.fr) {.testclass#testid}\r\n\r\nhttps:\/\/club1.fr\r\n\r\ntest de lien vers CLUB1<\/a>\r\n\r\ntest de lien vers CLUB1<\/a>\r\n\r\n