AVE.cms 3.3 build 0.31

This commit is contained in:
2026-07-27 12:58:44 +03:00
commit d6b981517c
1806 changed files with 379008 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
.DS_Store
# Runtime configuration
/configs/db.config.php
# Runtime state and secrets
/storage/*
!/storage/.htaccess
# Cache, logs and sessions
/tmp/*
!/tmp/.htaccess
# Uploaded files
/uploads/*
!/uploads/.htaccess
!/uploads/index.php
# Installed optional modules
/modules/*
+81
View File
@@ -0,0 +1,81 @@
# AVE.cms public root
Options -Indexes -MultiViews
DirectoryIndex index.php
AddDefaultCharset UTF-8
# Keep modern static formats portable across minimal Apache installations.
<IfModule mod_mime.c>
AddType image/avif avif
AddType image/webp webp
AddType font/woff2 woff2
AddType application/manifest+json webmanifest
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript
AddOutputFilterByType DEFLATE application/javascript application/json application/xml image/svg+xml
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 7 days"
ExpiresByType application/javascript "access plus 7 days"
ExpiresByType text/javascript "access plus 7 days"
ExpiresByType image/avif "access plus 30 days"
ExpiresByType image/gif "access plus 30 days"
ExpiresByType image/jpeg "access plus 30 days"
ExpiresByType image/png "access plus 30 days"
ExpiresByType image/svg+xml "access plus 30 days"
ExpiresByType image/webp "access plus 30 days"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
# Source maps, dumps, logs and environment files are never public downloads.
<IfModule mod_authz_core.c>
<FilesMatch "(^\.|\.(?:bak|backup|dist|ini|log|sql)$)">
Require all denied
</FilesMatch>
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Preserve bearer/basic credentials for the native API behind CGI/FastCGI.
RewriteCond %{HTTP:Authorization} .
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# AVE.cms uses the host without the legacy www prefix. Respect HTTPS both
# directly and behind a reverse proxy; TLS itself belongs to the web server.
RewriteCond %{HTTPS} =on [OR]
RewriteCond %{HTTP:X-Forwarded-Proto} =https [NC]
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L,NE]
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L,NE]
# The clean installer remains available before the database config exists.
RewriteRule ^setup/?$ setup/index.php [L,QSA]
# Internal implementation and runtime data must not be addressed directly.
RewriteRule ^(?:configs|storage|system|tmp)(?:/|$) - [R=404,L]
# Module packages expose only static files from their declared asset roots.
# Manifests, migrations, views, LESS sources and executable code stay private.
RewriteRule ^modules/[A-Za-z0-9_-]+/(?:(?:app|admin)/)?assets/.+\.(?:css|js|mjs|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|otf|eot|mp4|webm)$ - [L,NC]
RewriteRule ^modules(?:/|$) - [R=404,L,NC]
RewriteRule ^setup/(?!index\.php$).*\.php$ - [R=404,L,NC]
RewriteRule ^PACKAGE-MANIFEST\.json$ - [R=404,L,NC]
# Expose one canonical public front controller URL.
RewriteCond %{THE_REQUEST} \s/+index\.php(?:[?\s]) [NC]
RewriteRule ^index\.php$ / [R=301,L]
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L,QSA]
</IfModule>
File diff suppressed because it is too large Load Diff
+155
View File
@@ -0,0 +1,155 @@
# AVE.cms
**AVE.cms 3.3 build 0.31** — модульная система управления сайтами на PHP.
Она подходит для проектов, в которых структуру содержимого нужно собирать из
собственных рубрик, полей, документов, запросов, блоков и шаблонов, не
привязываясь к заранее заданному типу сайта.
AVE.cms включает публичное ядро и единую панель управления. `adminx/` — только
имя каталога панели по умолчанию: его можно изменить во время установки.
## Основные возможности
- конструктор рубрик и форм редактирования документов;
- расширяемый реестр типов полей;
- шаблоны сайта, рубрик, запросов, навигации и блоков;
- запросы с группами условий, сортировкой, пагинацией и предпросмотром;
- древовидная навигация с собственными шаблонами уровней;
- файловый браузер, редактор изображений, WebP и управляемые миниатюры;
- роли, права, пользователи панели и публичные аккаунты;
- ревизии содержимого, аудит действий и системные события;
- файловый и теговый кеш с контролируемой инвалидацией;
- HTTP API, хуки жизненного цикла и модульные расширения;
- защищённая публичная отладка для администраторов;
- обновления ядра и каталог устанавливаемых модулей;
- русская и английская локализация панели управления.
Публичная часть сайта остаётся одноязычной. Язык панели управления каждый
пользователь выбирает независимо.
## Как устроен контент
| Сущность | Назначение |
| --- | --- |
| **Шаблон сайта** | Формирует общий HTML-каркас страницы и подключает содержимое, навигацию, блоки и ассеты темы. |
| **Рубрика** | Описывает тип материала: набор полей, форму редактирования, правила URL и шаблоны вывода. |
| **Поле** | Хранит отдельную часть данных документа и определяет её редактор, проверку и публичное представление. |
| **Документ** | Содержит значения системных и пользовательских полей конкретной рубрики. |
| **Запрос** | Выбирает документы по условиям, сортирует их и выводит через шаблон списка и элемента. |
| **Навигация** | Собирает древовидные меню и управляет шаблонами каждого уровня. |
| **Блок** | Вставляет повторно используемый фрагмент содержимого или вычисляемый результат по тегу. |
| **Модуль** | Добавляет самостоятельную возможность, маршруты, права, настройки, хуки и элементы панели. |
При открытии страницы ядро находит документ, загружает его рубрику и поля,
применяет назначенные шаблоны, обрабатывает системные теги и подключает
навигацию, блоки и установленные модули. Подробная схема приведена в разделе
[«Как AVE.cms собирает сайт»](help/content/README.md).
## Требования
- PHP 7.3 или новее;
- MySQL 5.7 или новее с InnoDB и `utf8mb4`;
- расширения PHP `mysqli`, `mbstring`, `json` и `openssl`;
- расширения `curl`, `zip` и GD рекомендуются для сетевых операций, ZIP-пакетов
и обработки изображений;
- право записи в `configs/`, `storage/`, `tmp/` и `uploads/`;
- Apache с `mod_rewrite` либо эквивалентные правила маршрутизации веб-сервера.
Точные результаты проверки окружения установщик показывает до изменения базы
данных.
## Быстрая установка
1. Создайте пустую базу данных и пользователя с правами на её изменение.
2. Распакуйте архив AVE.cms в корневой каталог сайта, сохранив `.htaccess`.
3. Откройте сайт в браузере. При отсутствии конфигурации он направит вас на
`/setup/`.
4. Укажите параметры базы, префикс таблиц, язык панели и данные первого
администратора.
5. При необходимости измените имя каталога панели управления. По умолчанию это
`adminx`.
6. Завершите установку, войдите в панель и настройте структуру будущего сайта.
Установщик создаёт минимальный рабочий сайт: основной шаблон, рубрику с
заголовком и richtext-полем, главную страницу и служебную страницу 404.
Дополнительные возможности устанавливаются отдельно через раздел модулей.
Полная инструкция: [Установка AVE.cms](help/installation/README.md).
## Модули и расширение
Модули физически устанавливаются и удаляются. Пакет может добавить:
- публичные и административные маршруты;
- таблицы и последовательные миграции;
- пункты меню, действия в шапке и виджеты;
- права доступа и настройки;
- обработчики хуков и событий;
- публичные ассеты и шаблоны.
Модуль можно загрузить ZIP-архивом или получить из подключённого каталога.
Удаление модуля удаляет его файлы и принадлежащие ему данные согласно контракту
пакета.
Документация: [работа с модулями](help/modules/README.md).
## Структура проекта
```text
adminx/ панель управления и её разделы
configs/ конфигурация установленной системы
help/ пользовательская и публичная техническая документация
modules/ физически устанавливаемые модули
setup/ мастер чистой установки
storage/ служебные данные, секреты и состояние системы
system/App/ ядро AVE.cms
system/vendor/ встроенные сторонние библиотеки runtime
templates/ темы и публичные шаблоны
tmp/ кеш и временные файлы
uploads/ пользовательские медиафайлы
index.php публичная точка входа
```
Публичный запрос проходит через `index.php` и `App\Frontend\PublicKernel`.
Панель использует то же ядро, но имеет собственную точку входа и не определяет
работу публичной части. Проект использует собственный загрузчик и встроенные
библиотеки runtime: для установки готовой версии Composer и Node.js не нужны.
Новый PHP-код должен сохранять совместимость с PHP 7.3. Поддерживаемые точки
расширения описаны в разделах [HTTP API](help/api/README.md),
[хуки и события](help/hooks/README.md) и
[модульная система](help/modules/README.md).
## Документация
Начальная точка: [help/README.md](help/README.md).
- [управление системой](help/administration/README.md);
- [рубрики, документы, запросы и шаблоны](help/content/README.md);
- [поля документов](help/fields/README.md);
- [медиа и миниатюры](help/media/README.md);
- [модульная система](help/modules/README.md);
- [публичная отладка](help/debug/README.md);
- [безопасность](help/security/README.md);
- [работа с базой данных](help/database/README.md);
- [English documentation](help/en/README.md).
## Безопасность
- Не публикуйте `configs/db.config.php`, секреты модулей и содержимое
`storage/`.
- Оставляйте публичный Debug Toolbar доступным только доверенным
администраторам.
- Устанавливайте модули и обновления только из доверенного источника.
- Для Nginx вручную перенесите правила защиты `uploads/` и `modules/`,
приведённые в инструкции по установке.
- Перед обновлением рабочей системы создавайте проверенную резервную копию.
Об уязвимостях сообщайте непублично на `support@ave-cms.ru`.
## Лицензия
AVE.cms распространяется по лицензии GNU General Public License версии 2 или
любой более поздней версии по вашему выбору (`GPL-2.0-or-later`).
Полный текст находится в [license.txt](license.txt).
Официальный сайт: [ave-cms.ru](https://ave-cms.ru/)
+27
View File
@@ -0,0 +1,27 @@
###################### ADMINX_HTACCESS_BEGIN ##########################
# Роутинг панели: все несуществующие пути внутри текущего каталога заворачиваем
# на index.php (чистые URL). Реальные файлы (assets, css, js) отдаются
# напрямую. Правила подкаталога перекрывают корневой .htaccess для этого дерева.
Options -Indexes +FollowSymLinks
<IfModule mod_rewrite.c>
RewriteEngine On
# Only the front controller may be executed over HTTP. Module PHP files are
# include-only; their assets remain available through the file rule below.
RewriteRule ^(?!index\.php$).*\.php$ - [R=404,L]
# /<admin-directory>/modules совпадает с физической директорией модулей.
# Сам экран менеджера должен попасть во front controller, а вложенные
# modules/<Name>/assets/* ниже по-прежнему отдаются как реальные файлы.
RewriteRule ^modules/?$ index.php [L,QSA]
# Реальные файлы и директории — напрямую (css/js/картинки/аватары).
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Всё остальное — во фронт-контроллер админки.
RewriteRule . index.php [L,QSA]
</IfModule>
####################### ADMINX_HTACCESS_END ###########################
+342
View File
@@ -0,0 +1,342 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/AdminLocale.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\Language;
use App\Common\Session;
use App\Common\Settings;
/**
* Независимая локаль интерфейса Adminx.
*
* Публичный сайт остаётся одноязычным. Выбор панели хранится отдельно в
* admin_language и не изменяет PUBLIC_LANGUAGE/current_language.
*/
class AdminLocale
{
const DEFAULT_LOCALE = 'ru';
const SESSION_KEY = 'admin_language';
protected function __construct()
{
}
public static function current()
{
$locale = strtolower(trim((string) Session::get(self::SESSION_KEY)));
if (self::supports($locale)) {
return $locale;
}
try {
$locale = strtolower(trim((string) Settings::get('admin_default_language', self::DEFAULT_LOCALE)));
} catch (\Throwable $e) {
$locale = self::DEFAULT_LOCALE;
}
return self::supports($locale) ? $locale : self::DEFAULT_LOCALE;
}
public static function set($locale)
{
$locale = strtolower(trim((string) $locale));
if (!self::supports($locale)) {
return false;
}
Session::set(self::SESSION_KEY, $locale);
return true;
}
public static function supports($locale)
{
$locale = strtolower(trim((string) $locale));
return $locale !== '' && isset(self::locales()[$locale]);
}
/**
* Доступные языки определяются физическими словарями каркаса.
*/
public static function locales()
{
$labels = array(
'ru' => 'Русский',
'en' => 'English',
);
$locales = array();
$root = rtrim(ADMINX_PATH, '/\\') . DS . 'language';
$directories = is_dir($root) ? glob($root . DS . '*', GLOB_ONLYDIR) : array();
foreach ((array) $directories as $directory) {
$code = strtolower((string) basename($directory));
if (!preg_match('/^[a-z]{2}(?:-[a-z]{2})?$/', $code)
|| !glob($directory . DS . '*.xml')) {
continue;
}
$locales[$code] = isset($labels[$code]) ? $labels[$code] : strtoupper($code);
}
if (!isset($locales[self::DEFAULT_LOCALE])) {
$locales[self::DEFAULT_LOCALE] = $labels[self::DEFAULT_LOCALE];
}
return $locales;
}
/**
* Загрузить словари каркаса и зарегистрированных admin-модулей.
*
* Language сохраняет первый загруженный ключ, поэтому сначала идёт
* выбранная локаль, затем русский fallback только для отсутствующих фраз.
*/
public static function boot(array $modules = array())
{
Language::reset();
$locale = self::current();
$selected = self::readLocale($locale, $modules);
$source = $locale === self::DEFAULT_LOCALE
? $selected
: self::readLocale(self::DEFAULT_LOCALE, $modules);
Language::merge($selected);
if ($locale !== self::DEFAULT_LOCALE) {
Language::merge($source);
}
Language::$lang = $locale;
Language::setSourceTranslations(self::buildSourceTranslations($locale, $source, $selected));
}
public static function text($key, $fallback = '')
{
return Language::has($key) ? (string) Language::get($key) : (string) $fallback;
}
/**
* Перевести статичный фрагмент шаблона или служебное сообщение.
*
* Метод вызывается только для исходного текста интерфейса. Динамические
* названия документов, товаров и другие пользовательские данные через
* него не проходят.
*/
public static function translateMarkup($markup)
{
return Language::translateSource((string) $markup);
}
public static function sourceTranslations()
{
return Language::sourceTranslations();
}
/**
* Небольшой словарь стабильных ключей, которые читает общий JavaScript.
*/
public static function clientDictionary()
{
$keys = array(
'btn_cancel',
'btn_close',
'btn_confirm',
'btn_logout',
'confirm_action',
'confirm_title',
'field_current_password',
'help_load_error',
'help_loading',
'help_not_found',
'help_title',
'logout_message',
'logout_title',
'reauth_error',
'reauth_hint',
'reauth_reason',
'reauth_subtitle',
'reauth_title',
'server_unavailable',
'tag_copied',
);
$dictionary = array();
foreach ($keys as $key) {
if (Language::has($key)) {
$dictionary[$key] = (string) Language::get($key);
}
}
return $dictionary;
}
/**
* Исходные фразы только из client.xml загруженных модулей.
*
* Полный серверный словарь не встраивается в каждую HTML-страницу.
*/
public static function clientSourceTranslations(array $modules = array())
{
$locale = self::current();
if ($locale === self::DEFAULT_LOCALE) {
return array();
}
$source = self::readClientLocale(self::DEFAULT_LOCALE, $modules);
$target = self::readClientLocale($locale, $modules);
return self::buildSourceTranslations($locale, $source, $target);
}
/**
* Навигация переводится по стабильному коду, а не исходной подписи.
*/
public static function translateNavigation(array $items)
{
foreach ($items as &$item) {
$code = isset($item['code']) ? (string) $item['code'] : '';
if ($code !== '') {
$fallback = isset($item['label']) ? $item['label'] : $code;
$item['label'] = self::text('nav_' . $code, self::translateMarkup($fallback));
}
$group = isset($item['group']) ? (string) $item['group'] : '';
if ($group !== '') {
$item['group'] = self::text('nav_group_' . self::groupKey($group), self::translateMarkup($group));
}
}
unset($item);
return $items;
}
protected static function buildSourceTranslations($locale, array $source, array $target)
{
if ($locale === self::DEFAULT_LOCALE) {
return array();
}
$translations = array();
foreach ($source as $key => $value) {
if ($value === '' || !isset($target[$key]) || $target[$key] === '' || $target[$key] === $value) {
continue;
}
if (!isset($translations[$value])) {
$translations[$value] = $target[$key];
}
}
return $translations;
}
protected static function readLocale($locale, array $modules)
{
$directories = array(
rtrim(ADMINX_PATH, '/\\') . DS . 'language' . DS . $locale,
);
foreach ($modules as $module) {
$basePath = isset($module['base_path']) ? (string) $module['base_path'] : '';
if ($basePath !== '') {
$directories[] = rtrim($basePath, '/\\') . DS . 'language' . DS . $locale;
}
}
$phrases = array();
foreach ($directories as $directory) {
$files = is_dir($directory) ? glob(rtrim($directory, '/\\') . DS . '*.xml') : array();
sort($files, SORT_STRING);
foreach ($files as $file) {
$xml = @simplexml_load_file($file);
if (!$xml) {
continue;
}
foreach ($xml as $phrase) {
$key = trim((string) $phrase['data']);
if ($key !== '' && !isset($phrases[$key])) {
$phrases[$key] = (string) $phrase;
}
}
}
}
return $phrases;
}
protected static function readClientLocale($locale, array $modules)
{
$files = array(
rtrim(ADMINX_PATH, '/\\') . DS . 'language' . DS . $locale . DS . 'client.xml',
);
foreach ($modules as $module) {
$basePath = isset($module['base_path']) ? (string) $module['base_path'] : '';
if ($basePath !== '') {
$files[] = rtrim($basePath, '/\\') . DS . 'language' . DS . $locale . DS . 'client.xml';
}
}
$phrases = array();
foreach ($files as $file) {
if (!is_file($file)) {
continue;
}
$xml = @simplexml_load_file($file);
if (!$xml) {
continue;
}
foreach ($xml as $phrase) {
$key = trim((string) $phrase['data']);
if ($key !== '' && !isset($phrases[$key])) {
$phrases[$key] = (string) $phrase;
}
}
}
return $phrases;
}
protected static function groupKey($group)
{
$map = array(
'Обзор' => 'overview',
'Контент' => 'content',
'Каталог' => 'catalog',
'Продажи' => 'sales',
'Аналитика' => 'analytics',
'Интеграции' => 'integrations',
'Система' => 'system',
'Справка' => 'help',
'fields' => 'fields',
'modules_repository' => 'modules_repository',
'site_readiness' => 'site_readiness',
'source' => 'source',
);
return isset($map[$group]) ? $map[$group] : strtolower(preg_replace('/[^a-z0-9_-]+/i', '_', $group));
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/CodeEditor.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\AdminAssets;
/**
* Shared подключение CodeMirror 5 для source/code-полей новой админки.
*
* Библиотека принадлежит Adminx и хранится рядом с его общими assets.
*/
class CodeEditor
{
protected static $codeMirrorLoaded = false;
protected static $tiptapLoaded = false;
public static function useCodeMirror($mode = 'htmlmixed')
{
$mode = (string) $mode;
$base = ADMINX_BASE . '/assets/vendor/codemirror';
if (!self::$codeMirrorLoaded) {
AdminAssets::addStyle($base . '/lib/codemirror.css', 0);
AdminAssets::addStyle($base . '/addon/dialog/dialog.css', 0);
if (defined('CODEMIRROR_THEME') && CODEMIRROR_THEME !== '' && CODEMIRROR_THEME !== 'default') {
AdminAssets::addStyle($base . '/theme/' . rawurlencode(CODEMIRROR_THEME) . '.css', 0);
}
AdminAssets::addScript($base . '/lib/codemirror.js', 20);
AdminAssets::addScript($base . '/mode/xml/xml.js', 21);
AdminAssets::addScript($base . '/mode/javascript/javascript.js', 21);
AdminAssets::addScript($base . '/mode/css/css.js', 21);
AdminAssets::addScript($base . '/mode/clike/clike.js', 21);
AdminAssets::addScript($base . '/mode/php/php.js', 21);
AdminAssets::addScript($base . '/mode/smarty/smarty.js', 21);
AdminAssets::addScript($base . '/mode/smartymixed/smartymixed.js', 21);
AdminAssets::addScript($base . '/mode/htmlmixed/htmlmixed.js', 21);
AdminAssets::addScript($base . '/mode/sql/sql.js', 21);
AdminAssets::addScript($base . '/addon/edit/closetag.js', 22);
AdminAssets::addScript($base . '/addon/edit/matchbrackets.js', 22);
AdminAssets::addScript($base . '/addon/selection/active-line.js', 22);
AdminAssets::addScript($base . '/addon/dialog/dialog.js', 22);
AdminAssets::addScript($base . '/addon/search/searchcursor.js', 22);
AdminAssets::addScript($base . '/addon/search/search.js', 22);
AdminAssets::addScript(ADMINX_BASE . '/assets/js/editor-codemirror.js', 30);
self::$codeMirrorLoaded = true;
}
if ($mode !== '') {
self::useMode($mode);
}
}
public static function useTiptap()
{
if (self::$tiptapLoaded) {
return;
}
AdminAssets::addScript(ADMINX_BASE . '/assets/js/editor-tiptap.js', 31);
self::$tiptapLoaded = true;
}
public static function useRichEditor()
{
self::useCodeMirror('htmlmixed');
self::useTiptap();
}
protected static function useMode($mode)
{
$base = ADMINX_BASE . '/assets/vendor/codemirror';
if ($mode === 'text/css') {
AdminAssets::addScript($base . '/mode/css/css.js', 21);
} elseif ($mode === 'text/javascript' || $mode === 'application/json') {
AdminAssets::addScript($base . '/mode/javascript/javascript.js', 21);
} elseif ($mode === 'application/x-httpd-php' || $mode === 'php') {
AdminAssets::addScript($base . '/mode/php/php.js', 21);
} elseif ($mode === 'text/x-sql' || $mode === 'sql') {
AdminAssets::addScript($base . '/mode/sql/sql.js', 21);
}
}
}
+235
View File
@@ -0,0 +1,235 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/InterfaceSettings.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\ModuleSettings;
use App\Common\Settings;
/** Adminx-only order and visibility settings for navigation and dashboard. */
class InterfaceSettings
{
const MODULE = 'adminx_interface';
public static function navigationCatalog(array $items)
{
return self::navigationHierarchy(self::arrange('navigation', self::prepare($items, 'code'), false));
}
public static function applyNavigation(array $items)
{
return self::navigationHierarchy(self::arrange('navigation', self::prepare($items, 'code'), true));
}
public static function dashboardCatalog(array $items)
{
return self::arrange('dashboard', self::prepare($items, 'layout_code'), false);
}
public static function applyDashboard(array $items)
{
return self::arrange('dashboard', self::prepare($items, 'layout_code'), true);
}
public static function saveNavigation(array $rows, array $available)
{
return self::save('navigation', $rows, $available);
}
public static function saveDashboard(array $rows, array $available)
{
$saved = self::save('dashboard', $rows, $available);
$byCode = array();
foreach ($available as $item) {
if (!empty($item['layout_code'])) {
$byCode[(string) $item['layout_code']] = $item;
}
}
$moduleVisibility = array();
foreach ($saved as $row) {
$code = (string) $row['code'];
if (isset($byCode[$code]) && !empty($byCode[$code]['module_code'])) {
$moduleCode = (string) $byCode[$code]['module_code'];
$moduleVisibility[$moduleCode] = !empty($moduleVisibility[$moduleCode]) || !empty($row['visible']);
}
}
foreach ($moduleVisibility as $moduleCode => $visible) {
ModuleSettings::set('adminx_dashboard_enabled', $visible, $moduleCode, 'bool');
}
return $saved;
}
protected static function prepare(array $items, $codeKey)
{
$prepared = array();
foreach ($items as $index => $item) {
if (!is_array($item) || empty($item[$codeKey])) {
continue;
}
$item['layout_code'] = (string) $item[$codeKey];
if (!isset($item['sort_order'])) {
$item['sort_order'] = ($index + 1) * 10;
}
if (!isset($item['default_visible'])) {
$item['default_visible'] = true;
}
$prepared[] = $item;
}
return $prepared;
}
protected static function arrange($scope, array $items, $visibleOnly)
{
$config = self::config($scope);
$position = array();
$visibility = array();
foreach ($config as $index => $row) {
$position[$row['code']] = $index;
$visibility[$row['code']] = !empty($row['visible']);
}
foreach ($items as &$item) {
$code = (string) $item['layout_code'];
$item['layout_visible'] = isset($visibility[$code])
? $visibility[$code]
: !empty($item['default_visible']);
$item['_layout_position'] = isset($position[$code]) ? $position[$code] : null;
}
unset($item);
usort($items, function ($a, $b) {
$ap = $a['_layout_position'];
$bp = $b['_layout_position'];
if ($ap !== null || $bp !== null) {
if ($ap === null) return 1;
if ($bp === null) return -1;
if ($ap !== $bp) return $ap < $bp ? -1 : 1;
}
$as = isset($a['sort_order']) ? (int) $a['sort_order'] : 1000;
$bs = isset($b['sort_order']) ? (int) $b['sort_order'] : 1000;
if ($as !== $bs) return $as < $bs ? -1 : 1;
return strcmp((string) $a['layout_code'], (string) $b['layout_code']);
});
foreach ($items as &$item) {
unset($item['_layout_position']);
}
unset($item);
if (!$visibleOnly) {
return $items;
}
return array_values(array_filter($items, function ($item) {
return !empty($item['layout_visible']);
}));
}
protected static function navigationHierarchy(array $items)
{
$roots = array();
$children = array();
foreach ($items as $item) {
$parent = isset($item['parent']) ? (string) $item['parent'] : '';
if ($parent === '') {
$roots[] = $item;
} else {
if (!isset($children[$parent])) {
$children[$parent] = array();
}
$children[$parent][] = $item;
}
}
$result = array();
foreach ($roots as $root) {
$result[] = $root;
$code = (string) $root['layout_code'];
if (isset($children[$code])) {
foreach ($children[$code] as $child) {
$result[] = $child;
}
}
}
return $result;
}
protected static function save($scope, array $rows, array $available)
{
$allowed = array();
foreach ($available as $item) {
$code = isset($item['layout_code']) ? (string) $item['layout_code'] : (isset($item['code']) ? (string) $item['code'] : '');
if ($code !== '') {
$allowed[$code] = true;
}
}
$result = array();
$seen = array();
foreach ($rows as $row) {
$code = is_array($row) && isset($row['code']) ? trim((string) $row['code']) : '';
if ($code === '' || !isset($allowed[$code]) || isset($seen[$code])) {
continue;
}
$seen[$code] = true;
$result[] = array('code' => $code, 'visible' => !empty($row['visible']));
}
foreach (array_keys($allowed) as $code) {
if (!isset($seen[$code])) {
$result[] = array('code' => $code, 'visible' => true);
}
}
Settings::set(self::key($scope), $result, 'json');
return $result;
}
protected static function config($scope)
{
$value = Settings::get(self::key($scope), array());
if (!is_array($value)) {
return array();
}
$result = array();
foreach ($value as $row) {
if (is_array($row) && !empty($row['code'])) {
$result[] = array('code' => (string) $row['code'], 'visible' => !empty($row['visible']));
}
}
return $result;
}
protected static function key($scope)
{
return self::MODULE . '.' . (string) $scope;
}
}
+293
View File
@@ -0,0 +1,293 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/ModuleExtensions.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\ModuleManager;
use App\Common\ModuleSettings;
use App\Common\Navigation;
use App\Common\Permission;
/**
* UI-реестр adminx-модулей.
*
* Ядро хранит neutral descriptor, а этот слой подключает только adminx-вклады:
* меню, действия шапки, dashboard-виджеты и уведомления.
*/
class ModuleExtensions
{
protected static $booted = false;
protected static $extensions = array();
public static function boot()
{
if (self::$booted) {
return;
}
self::$booted = true;
foreach (ModuleManager::all() as $module) {
if (empty($module['admin_extension']) || !is_array($module['admin_extension'])) {
continue;
}
self::$extensions[$module['code']] = array(
'module' => $module,
'config' => $module['admin_extension'],
);
if (!empty($module['installed']) && !empty($module['enabled'])) {
self::registerMenu($module['admin_extension']);
}
}
}
public static function headerActions()
{
return self::contributions('header');
}
public static function dashboardWidgets(array $visibleCodes = null)
{
return self::contributions('dashboard', $visibleCodes);
}
/** Dashboard descriptors without running providers; used by interface settings. */
public static function dashboardDefinitions()
{
self::boot();
$out = array();
foreach (self::$extensions as $code => $extension) {
$module = self::currentModule($code, $extension['module']);
if (empty($module['installed']) || empty($module['enabled'])) {
continue;
}
$config = $extension['config'];
if (empty($config['dashboard'])) {
continue;
}
foreach (self::normalizeItems($config['dashboard']) as $index => $item) {
if (!is_array($item)) {
continue;
}
$out[] = array(
'layout_code' => self::contributionCode($code, 'dashboard', $item, $index),
'module_code' => $code,
'label' => AdminLocale::translateMarkup(isset($item['label']) ? (string) $item['label'] : (isset($module['name']) ? (string) $module['name'] : $code)),
'description' => AdminLocale::translateMarkup(isset($item['description']) ? (string) $item['description'] : (isset($module['description']) ? (string) $module['description'] : '')),
'icon' => isset($item['icon']) ? (string) $item['icon'] : (isset($config['icon']) ? (string) $config['icon'] : 'ti ti-layout-dashboard'),
'sort_order' => 100 + (isset($item['sort_order']) ? (int) $item['sort_order'] : 100),
'default_visible' => self::presentationEnabled($code, 'dashboard'),
);
}
}
return $out;
}
public static function notifications($base, array $summary)
{
foreach (self::contributions('notifications') as $contribution) {
$data = isset($contribution['data']) ? $contribution['data'] : array();
$items = isset($data['items']) && is_array($data['items']) ? $data['items'] : (self::isList($data) ? $data : array());
foreach ($items as $item) {
if (!is_array($item) || empty($item['title'])) {
continue;
}
$count = isset($item['count']) ? max(0, (int) $item['count']) : 1;
if ($count < 1) {
continue;
}
$url = isset($item['url']) ? (string) $item['url'] : '';
if ($url !== '' && $url[0] === '/') {
$url = rtrim((string) $base, '/') . $url;
}
$summary['items'][] = array_merge(array(
'icon' => 'ti ti-bell',
'bg' => '#e8f1ff',
'fg' => '#2563eb',
'text' => '',
'url' => $url,
'count' => $count,
), $item, array(
'url' => $url,
'count' => $count,
'text' => AdminLocale::translateMarkup(isset($item['text']) ? (string) $item['text'] : ''),
'title' => AdminLocale::translateMarkup(isset($item['title']) ? (string) $item['title'] : ''),
));
$summary['total'] += $count;
if (!empty($item['key'])) {
$key = (string) $item['key'];
$summary[$key] = isset($summary[$key]) ? (int) $summary[$key] + $count : $count;
}
}
}
return $summary;
}
public static function catalog()
{
self::boot();
$items = array();
foreach (self::$extensions as $code => $extension) {
$module = self::currentModule($code, $extension['module']);
$config = $extension['config'];
$items[] = array(
'code' => $code,
'name' => AdminLocale::translateMarkup(isset($module['name']) ? $module['name'] : $code),
'description' => AdminLocale::translateMarkup(isset($module['description']) ? $module['description'] : ''),
'author' => isset($module['author']) ? $module['author'] : '',
'admin_url' => isset($config['url']) ? (string) $config['url'] : '',
'feature' => isset($config['feature']) ? (string) $config['feature'] : '',
'icon' => isset($config['icon']) ? (string) $config['icon'] : 'ti ti-puzzle',
'module' => $module,
);
}
return $items;
}
protected static function contributions($type, array $visibleCodes = null)
{
self::boot();
$out = array();
$visible = $visibleCodes === null ? null : array_fill_keys($visibleCodes, true);
foreach (self::$extensions as $code => $extension) {
$module = self::currentModule($code, $extension['module']);
if (empty($module['installed']) || empty($module['enabled'])) {
continue;
}
$config = $extension['config'];
if (empty($config[$type])) {
continue;
}
if (($type === 'header' || $type === 'dashboard') && !self::presentationEnabled($code, $type)) {
continue;
}
foreach (self::normalizeItems($config[$type]) as $index => $item) {
if (!is_array($item) || !self::allowed($item)) {
continue;
}
$layoutCode = self::contributionCode($code, $type, $item, $index);
if ($visible !== null && !isset($visible[$layoutCode])) {
continue;
}
try {
$data = isset($item['provider']) && is_callable($item['provider'])
? call_user_func($item['provider'])
: (isset($item['data']) ? $item['data'] : array());
} catch (\Throwable $e) {
$data = array();
}
$item['module_code'] = $code;
$item['layout_code'] = $layoutCode;
$item['label'] = AdminLocale::translateMarkup(isset($item['label']) ? (string) $item['label'] : (isset($module['name']) ? (string) $module['name'] : $code));
if (isset($item['description'])) {
$item['description'] = AdminLocale::translateMarkup((string) $item['description']);
}
$item['icon'] = isset($item['icon']) ? (string) $item['icon'] : (isset($config['icon']) ? (string) $config['icon'] : 'ti ti-layout-dashboard');
$item['data'] = is_array($data) ? $data : array();
$item['sort_order'] = isset($item['sort_order']) ? (int) $item['sort_order'] : 100;
$out[] = $item;
}
}
usort($out, function ($a, $b) {
return $a['sort_order'] <=> $b['sort_order'];
});
return $out;
}
protected static function contributionCode($moduleCode, $type, array $item, $index)
{
$suffix = isset($item['code']) && trim((string) $item['code']) !== ''
? trim((string) $item['code'])
: (string) ((int) $index + 1);
return 'module.' . (string) $moduleCode . '.' . (string) $type . '.' . $suffix;
}
protected static function registerMenu(array $config)
{
if (empty($config['menu'])) {
return;
}
$items = is_callable($config['menu']) ? call_user_func($config['menu']) : $config['menu'];
if (is_array($items) && !self::isList($items)) {
$items = array($items);
}
if (is_array($items)) {
Navigation::addMany($items);
}
}
protected static function normalizeItems($items)
{
if (!is_array($items)) {
return array();
}
return self::isList($items) ? $items : array($items);
}
protected static function isList(array $items)
{
return empty($items) || array_keys($items) === range(0, count($items) - 1);
}
protected static function allowed(array $item)
{
$permission = isset($item['permission']) ? (string) $item['permission'] : '';
if ($permission !== '' && !Permission::check($permission)) {
return false;
}
return empty($item['visible']) || !is_callable($item['visible']) || (bool) call_user_func($item['visible']);
}
protected static function currentModule($code, array $fallback)
{
foreach (ModuleManager::all() as $module) {
if ($module['code'] === (string) $code) {
return $module;
}
}
return $fallback;
}
/** Управляемая администратором видимость UI-вклада модуля. */
protected static function presentationEnabled($code, $type)
{
return (bool) ModuleSettings::get('adminx_' . $type . '_enabled', true, $code);
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/Notifications.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\Permission;
/**
* Пустая системная сводка. Прикладные источники подключаются владельцами
* через ModuleExtensions и исчезают вместе с модулем.
*/
class Notifications
{
/** @return array{total:int,orders:int,messages:int,items:array} */
public static function summary($base)
{
$summary = array(
'total' => 0,
'orders' => 0,
'messages' => 0,
'items' => array(),
);
$sqlErrors = SystemHealth::sqlErrorCount();
if ($sqlErrors > 0 && Permission::check('view_events')) {
$summary['total'] += $sqlErrors;
$summary['database_errors'] = $sqlErrors;
$summary['items'][] = array(
'key' => 'database_errors',
'title' => AdminLocale::text('notification_database_errors', 'Ошибки базы данных'),
'text' => AdminLocale::text(
'notification_database_errors_hint',
'Проверьте SQL-журнал системных событий.'
),
'url' => rtrim((string) $base, '/') . '/events?source=sql',
'count' => $sqlErrors,
'icon' => 'ti ti-database-exclamation',
'bg' => '#feecec',
'fg' => '#dc2626',
);
}
return $summary;
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/PhpCode.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
class PhpCode
{
public static function source($code)
{
$code = trim((string) $code);
if (preg_match('/^<\?(?:php|=)?/i', $code)) {
return $code;
}
return "<?php\n" . $code . "\n";
}
public static function lint($code)
{
$directory = BASEPATH . '/tmp/console';
if (!is_dir($directory) && !@mkdir($directory, 0775, true)) {
return array('ok' => false, 'message' => 'Не удалось создать временный файл.', 'output' => '', 'line' => 0);
}
$file = $directory . '/lint-' . bin2hex(random_bytes(8)) . '.php';
if (@file_put_contents($file, self::source($code)) === false) {
@unlink($file);
return array('ok' => false, 'message' => 'Не удалось записать временный файл.', 'output' => '', 'line' => 0);
}
$php = defined('PHP_BINARY') && PHP_BINARY ? PHP_BINARY : 'php';
$output = array();
$status = 0;
@exec(escapeshellarg($php) . ' -l ' . escapeshellarg($file) . ' 2>&1', $output, $status);
@unlink($file);
$text = trim(implode("\n", $output));
$text = str_replace($file, 'код', $text);
$line = 0;
if (preg_match('/ on line ([0-9]+)/i', $text, $match)) {
$line = (int) $match[1];
}
return array(
'ok' => $status === 0 && $text !== '',
'message' => $status === 0 ? 'Синтаксис PHP без ошибок.' : 'В PHP-коде есть синтаксическая ошибка.',
'output' => $text,
'line' => $line,
);
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/ReAuth.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\Auth;
use App\Common\Session;
/** Short-lived password confirmation for high-risk Adminx actions. */
class ReAuth
{
const DEFAULT_TTL = 300;
const MAX_TTL = 900;
const SESSION_KEY = '_adminx_reauth';
public static function confirm($password)
{
$user = Auth::user();
$hash = self::passwordHash($user);
if (!$user || $hash === '' || !password_verify((string) $password, $hash)) {
self::forget();
return false;
}
Session::set(self::SESSION_KEY, array(
'user_id' => (int) $user['id'],
'confirmed_at' => time(),
'password_fingerprint' => hash('sha256', $hash),
));
return true;
}
public static function valid($ttl = self::DEFAULT_TTL)
{
$user = Auth::user();
$state = Session::get(self::SESSION_KEY);
$hash = self::passwordHash($user);
$ttl = self::normalizeTtl($ttl);
if (!$user || !is_array($state) || $hash === '') {
return false;
}
$valid = isset($state['user_id'], $state['confirmed_at'], $state['password_fingerprint'])
&& (int) $state['user_id'] === (int) $user['id']
&& (int) $state['confirmed_at'] >= time() - $ttl
&& hash_equals((string) $state['password_fingerprint'], hash('sha256', $hash));
if (!$valid) {
self::forget();
}
return $valid;
}
public static function forget()
{
Session::del(self::SESSION_KEY);
}
public static function normalizeTtl($ttl)
{
return max(30, min(self::MAX_TTL, (int) $ttl));
}
protected static function passwordHash($user)
{
return is_array($user) && isset($user['password_hash'])
? trim((string) $user['password_hash'])
: '';
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/Roles.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use DB;
use App\Common\SystemTables;
/**
* Общий справочник ролей: расшифровка code → человекочитаемое имя.
*
* Источник — таблица {prefix}_roles (RBAC). Используется везде, где роль
* показывается пользователю (список/фильтры/формы), чтобы не светить голый код
* (`admin` → «Администратор»).
*/
class Roles
{
/** @var array|null кеш карты на запрос */
protected static $map = null;
/** Фолбэк-имена, если таблица ролей ещё не создана. */
protected static function fallback()
{
return array(
'admin' => 'Администратор',
'guest' => 'Гостевая',
'moderator' => 'Модератор',
'user' => 'Пользователи',
);
}
/** Карта code => name (из БД, иначе фолбэк). */
public static function map()
{
if (self::$map !== null) {
return self::$map;
}
$map = array();
try {
$exists = DB::query('SHOW TABLES LIKE %s', SystemTables::table('roles'))->getValue();
if ($exists) {
$rows = DB::query('SELECT code, name FROM ' . SystemTables::table('roles') . ' ORDER BY is_system DESC, name ASC')->getAll();
foreach ($rows ?: array() as $row) {
$row = (array) $row;
$code = trim((string) $row['code']);
if ($code !== '') {
$map[$code] = (string) $row['name'] !== '' ? (string) $row['name'] : $code;
}
}
}
} catch (\Throwable $e) {
$map = array();
}
self::$map = $map ?: self::fallback();
return self::$map;
}
/** Имя роли по коду (или сам код, если не найдено). */
public static function name($code)
{
$code = (string) $code;
$map = self::map();
return isset($map[$code]) ? $map[$code] : $code;
}
/** Валидные коды ролей. */
public static function codes()
{
return array_keys(self::map());
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/RouteGuard.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\ModuleManager;
use App\Common\Permission;
use App\Common\Session;
use App\Common\Settings;
use App\Common\Twig;
use App\Helpers\Request;
use App\Helpers\Response;
/** Enforces route permissions and CSRF before an Adminx controller is invoked. */
class RouteGuard
{
public static function enforce(array $route, $isPublic = false)
{
$options = isset($route['options']) && is_array($route['options']) ? $route['options'] : array();
try {
SensitiveRoutes::validate($route);
} catch (\InvalidArgumentException $e) {
self::deny(500, 'Ошибка политики маршрута', $e->getMessage());
}
$permission = isset($options['permission']) ? trim((string) $options['permission']) : '';
if (!$isPublic && $permission !== '' && !Permission::checkAcp($permission)) {
self::deny(403, 'Доступ запрещён', 'У вашей роли нет права на просмотр этого раздела.');
}
$method = isset($route['method']) ? strtoupper((string) $route['method']) : Request::method();
$csrfRequired = !isset($options['csrf']) || $options['csrf'] !== false;
if ($csrfRequired && !in_array($method, array('GET', 'HEAD', 'OPTIONS'), true)) {
$token = (string) Request::header('X-CSRF-Token', '');
if ($token === '') {
$token = Request::postStr('_csrf', '');
}
try {
Session::verifyCsrf($token);
} catch (\Throwable $e) {
self::deny(419, 'Сессия устарела', 'Обновите страницу и повторите действие.');
}
}
if (!$isPublic && !empty($options['reauth']) && (bool) Settings::get('adminx_reauth_enabled', false)) {
$config = is_array($options['reauth']) ? $options['reauth'] : array();
$ttl = isset($config['ttl']) ? ReAuth::normalizeTtl($config['ttl']) : ReAuth::DEFAULT_TTL;
if (!ReAuth::valid($ttl)) {
$reason = isset($config['reason']) && trim((string) $config['reason']) !== ''
? trim((string) $config['reason'])
: 'Это действие изменяет исполняемый код системы.';
self::deny(428, 'Подтвердите пароль', 'Для продолжения подтвердите пароль текущего пользователя.', array(
'reauth_required' => true,
'reauth_url' => (defined('ADMINX_BASE') ? ADMINX_BASE : '') . '/reauth',
'reason' => $reason,
'ttl' => $ttl,
));
}
}
}
protected static function deny($status, $title, $message, array $data = array())
{
$accept = strtolower((string) Request::header('Accept', ''));
if (Request::isAjax() || strpos($accept, 'application/json') !== false) {
Response::json(array(
'success' => false,
'message' => (string) $message,
'data' => empty($data) ? new \stdClass() : $data,
'errors' => new \stdClass(),
), (int) $status);
}
Response::setStatus((int) $status);
echo Twig::twig()->render('@adminx/error.twig', array_merge(ModuleManager::viewGlobals(), array(
'status' => (int) $status,
'title' => (string) $title,
'message' => (string) $message,
)));
Request::shutDown();
}
}
+209
View File
@@ -0,0 +1,209 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/SavedViews.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\SystemTables;
use DB;
/** Персональные именованные наборы фильтров для рабочих списков Adminx. */
class SavedViews
{
const MAX_VIEWS = 20;
public static function all($scope, $userId, array $allowedFields)
{
$scope = self::scope($scope);
$userId = self::userId($userId);
$rows = DB::query(
'SELECT id,title,filters_json,updated_at FROM ' . self::table() . ' WHERE user_id=%i AND scope=%s ORDER BY title,id',
$userId,
$scope
)->getAll();
$result = array();
foreach ((array) $rows as $row) {
$id = is_array($row) && isset($row['id']) ? self::id($row['id']) : '';
$title = is_array($row) && isset($row['title']) ? self::title($row['title']) : '';
if ($id === '' || $title === '') {
continue;
}
$filters = json_decode(isset($row['filters_json']) ? (string) $row['filters_json'] : '{}', true);
$result[] = array(
'id' => $id,
'title' => $title,
'filters' => self::filters(is_array($filters) ? $filters : array(), $allowedFields),
'updated_at' => isset($row['updated_at']) ? (int) $row['updated_at'] : 0,
);
}
return array_slice($result, 0, self::MAX_VIEWS);
}
public static function save($scope, $userId, $title, array $filters, array $allowedFields)
{
$title = self::title($title);
if ($title === '') {
throw new \InvalidArgumentException('Укажите название представления');
}
$scope = self::scope($scope);
$userId = self::userId($userId);
$rows = self::all($scope, $userId, $allowedFields);
$normalized = self::filters($filters, $allowedFields);
$matchedId = '';
foreach ($rows as $row) {
if (mb_strtolower($row['title'], 'UTF-8') !== mb_strtolower($title, 'UTF-8')) {
continue;
}
$matchedId = (string) $row['id'];
break;
}
$data = array(
'title' => $title,
'filters_json' => self::json($normalized),
'updated_at' => time(),
);
if ($matchedId !== '') {
DB::Update(self::table(), $data, 'id=%s AND user_id=%i AND scope=%s', $matchedId, $userId, $scope);
} else {
if (count($rows) >= self::MAX_VIEWS) {
throw new \InvalidArgumentException('Можно сохранить не более ' . self::MAX_VIEWS . ' представлений');
}
DB::Insert(self::table(), array_merge($data, array(
'id' => self::newId(),
'user_id' => $userId,
'scope' => $scope,
)));
}
return self::all($scope, $userId, $allowedFields);
}
public static function delete($scope, $userId, $id, array $allowedFields)
{
$id = self::id($id);
if ($id === '') {
throw new \InvalidArgumentException('Представление не найдено');
}
$scope = self::scope($scope);
$userId = self::userId($userId);
$exists = (int) DB::query(
'SELECT COUNT(*) FROM ' . self::table() . ' WHERE id=%s AND user_id=%i AND scope=%s',
$id,
$userId,
$scope
)->getValue();
if (!$exists) {
throw new \InvalidArgumentException('Представление не найдено');
}
DB::Delete(self::table(), 'id=%s AND user_id=%i AND scope=%s', $id, $userId, $scope);
return self::all($scope, $userId, $allowedFields);
}
protected static function filters($filters, array $allowedFields)
{
if (!is_array($filters)) {
return array();
}
$allowed = array_fill_keys($allowedFields, true);
$result = array();
foreach ($filters as $name => $value) {
$name = (string) $name;
if (!isset($allowed[$name]) || is_array($value) || is_object($value)) {
continue;
}
$value = trim((string) $value);
if (mb_strlen($value, 'UTF-8') > 500) {
$value = mb_substr($value, 0, 500, 'UTF-8');
}
$result[$name] = $value;
}
foreach ($allowedFields as $name) {
if (!array_key_exists($name, $result)) {
$result[$name] = '';
}
}
return $result;
}
protected static function table()
{
return SystemTables::table('admin_saved_views');
}
protected static function scope($scope)
{
$scope = strtolower(trim((string) $scope));
if (!preg_match('/^[a-z0-9_.-]{2,64}$/', $scope)) {
throw new \InvalidArgumentException('Некорректная область представления');
}
return $scope;
}
protected static function userId($userId)
{
$userId = (int) $userId;
if ($userId <= 0) {
throw new \InvalidArgumentException('Пользователь не определён');
}
return $userId;
}
protected static function title($title)
{
$title = preg_replace('/\s+/u', ' ', trim((string) $title));
return mb_substr($title, 0, 60, 'UTF-8');
}
protected static function id($id)
{
$id = strtolower(trim((string) $id));
return preg_match('/^[a-f0-9]{12}$/', $id) ? $id : '';
}
protected static function newId()
{
try {
return bin2hex(random_bytes(6));
} catch (\Throwable $e) {
return substr(sha1(uniqid('', true) . mt_rand()), 0, 12);
}
}
protected static function json(array $value)
{
$json = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json === false) {
throw new \InvalidArgumentException('Не удалось подготовить фильтры');
}
return $json;
}
}
+175
View File
@@ -0,0 +1,175 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/SectionHelp.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
/** Contextual help shown on Adminx section pages. */
class SectionHelp
{
public static function forPath($path)
{
$path = '/' . trim((string) $path, '/');
$moduleHelp = self::moduleDefinition($path);
if ($moduleHelp !== null) {
return $moduleHelp;
}
foreach (self::definitions() as $definition) {
if (preg_match($definition['pattern'], $path)) {
unset($definition['pattern']);
return $definition;
}
}
return null;
}
protected static function moduleDefinition($path)
{
foreach (\App\Common\ModuleManager::all() as $module) {
$extension = isset($module['admin_extension']) && is_array($module['admin_extension']) ? $module['admin_extension'] : array();
$url = isset($extension['url']) ? '/' . trim((string) $extension['url'], '/') : '';
$help = isset($extension['help']) && is_array($extension['help']) ? $extension['help'] : array();
if ($url === '' || !$help || ($path !== $url && strpos($path, $url . '/') !== 0)) {
continue;
}
$title = trim(isset($help['title']) ? (string) $help['title'] : (isset($module['name']) ? (string) $module['name'] : ''));
$description = trim(isset($help['description']) ? (string) $help['description'] : '');
$items = array();
foreach (isset($help['items']) && is_array($help['items']) ? $help['items'] : array() as $item) {
$item = trim((string) $item);
if ($item !== '') {
$items[] = $item;
}
}
if ($title === '' || $description === '' || !$items) {
return null;
}
return array(
'title' => AdminLocale::translateMarkup($title),
'description' => AdminLocale::translateMarkup($description),
'items' => array_map(array(AdminLocale::class, 'translateMarkup'), $items),
'note' => AdminLocale::translateMarkup(trim(isset($help['note']) ? (string) $help['note'] : '')),
'doc' => trim(isset($help['doc']) ? (string) $help['doc'] : ''),
);
}
return null;
}
protected static function definitions()
{
return array(
self::item('#^/documents/redirects$#', 'Редиректы документов', 'Управляет старыми адресами страниц и перенаправляет посетителей на актуальные документы.', array('Ищите редирект по старому URL, названию или ID документа.', 'Коды 301/308 подходят для постоянного переноса, 302/307 — для временного.', 'Перед удалением убедитесь, что старый адрес больше не используется во внешних ссылках.'), 'Каждый старый URL должен быть уникальным и вести на существующий документ.'),
self::item('#^/documents/views$#', 'Статистика просмотров', 'Показывает посещаемость документов по дням и помогает находить наиболее востребованные страницы.', array('Период влияет только на подневную статистику.', 'Общий счётчик документа хранится отдельно и не очищается вместе с дневными данными.', 'Очистка статистики необратима и предназначена для обслуживания.'), 'Для бизнес-аналитики используйте внешнюю систему аналитики; здесь хранится внутренняя статистика CMS.'),
self::item('#^/documents/(?:create|\d+/edit)$#', 'Редактор документа', 'Создаёт и изменяет страницу сайта: её адрес, публикацию, SEO и значения полей выбранной рубрики.', array('Набор полей и их группы задаются рубрикой.', 'Изменение alias может автоматически создать редирект со старого URL.', 'Ctrl+S сохраняет изменения и оставляет редактор открытым; ревизии позволяют вернуться к прошлой версии.'), 'Смена рубрики существующего документа запрещена без отдельной миграции полей.'),
self::item('#^/documents#', 'Документы', 'Основной реестр страниц и товарных карточек сайта, построенных на рубриках и их полях.', array('Фильтруйте документы по рубрике, состоянию и названию.', 'Публикацию, копирование и удаление можно выполнять из списка или пакетно.', 'Удалённый документ сначала попадает в корзину и только затем может быть удалён окончательно.'), 'Документ №1 и системная страница 404 защищены от окончательного удаления.', 'content/documents'),
self::item('#^/catalog/products/shipping$#', 'Доставка и упаковка', 'Показывает готовность товарных данных к расчёту доставки и позволяет редактировать грузовые места без открытия документа.', array('Каждое грузовое место хранит количество, вес и три габарита.', 'Расчёт включается отдельно и использует только полностью заполненную упаковку.', 'Фильтр помогает быстро найти товары без размеров или с выключенным расчётом.'), 'Вес и размеры указываются для одного грузового места; количество умножает итоговые показатели.'),
self::item('#^/catalog/products(?:/.*)?$#', 'Товары каталога', 'Показывает товарные документы в быстром индексе каталога: цены, остатки, изображения и категории.', array('Переиндексация обновляет проекцию товара из полей документа.', 'Редактор товара остаётся редактором документа, но открывается из каталога.', 'Фильтры списка не меняют данные товара.'), 'После изменения каталоговых полей индекс обычно обновляется автоматически.'),
self::item('#^/catalog/quick-edit$#', 'Быстрое заполнение товаров', 'Позволяет массово уточнять основные товарные данные прямо в таблице без открытия каждого документа.', array('Изменённая ячейка сохраняется отдельно по AJAX.', 'Фильтры помогают отобрать товары без артикула, цены или обязательных признаков.', 'Состояние строки показывает публикацию и доступность товара.'), 'Используйте редактор документа для полей, которых нет в таблице быстрого заполнения.'),
self::item('#^/catalog/variant-groups(?:/.*)?$#', 'Группы вариантов', 'Объединяет отдельные товары с собственными URL и артикулами в одну карточку публичного листинга.', array('Основной вариант представляет группу до выбора цвета или исполнения.', 'Порядок вариантов определяет порядок переключателей на публичной карточке.', 'Выключенный вариант остаётся документом, но не предлагается покупателю.'), 'Каждый вариант должен сохранять собственные цену, остаток, URL и артикул.'),
self::item('#^/catalog/card-templates$#', 'Карточки товаров', 'Управляет Twig-разметкой и CSS товарной карточки отдельно для разных мест публичного сайта.', array('Черновик можно проверять на живых данных товара без публикации.', 'Контексты назначают представление главной странице, каталогу и другим листингам.', 'Ревизии сохраняют историю черновиков и опубликованных версий.'), 'Публичный сайт меняется только после публикации представления и включения нативного режима нужного контекста.'),
self::item('#^/catalog/filter-templates$#', 'Шаблоны фильтров', 'Управляет Twig-разметкой, CSS и клиентским поведением фасетов товарного каталога.', array('Алгоритм выборки остаётся в каталоге, а шаблон отвечает за представление.', 'Черновик можно проверить до публикации.', 'Теги шаблона выводят название фильтра, варианты и рассчитанные количества товаров.'), 'После публикации проверьте выбор, сброс и повторное применение нескольких фильтров.'),
self::item('#^/catalog/\d+/\d+$#', 'Конструктор каталога', 'Связывает поле типа catalog с деревом разделов, документами, полями и публичными фильтрами.', array('Разделы задают структуру каталога и набор доступных полей.', 'Условия фильтров определяют SQL-ограничения публичной выборки.', 'Порядок полей и фильтров меняется перетаскиванием и сохраняется сразу.'), 'Изменения структуры влияют на редактор документов и публичный каталог.'),
self::item('#^/catalog#', 'Каталог', 'Управляет структурами каталогов, товарным индексом, фильтрами и связью с документами.', array('Каждый каталог создаётся для конкретного поля рубрики типа catalog.', 'Товары являются документами и не дублируются в отдельной основной таблице.', 'Для вариантов одного товара используются группы вариантов.'), 'Начинайте с выбора каталога, затем настраивайте разделы, поля и фильтры.', 'content/catalog'),
self::item('#^/rubrics(?:/.*)?$#', 'Рубрики и поля', 'Определяет типы документов: набор полей, группы, шаблоны вывода, права и код жизненного цикла.', array('Поля являются отдельными сущностями со своим типом, настройками и шаблоном.', 'Группы организуют длинные формы документов и не влияют на публичный URL.', 'Код до/после сохранения выполняется при изменении документа и требует проверки синтаксиса.'), 'Удаляйте рубрики и поля только после проверки связанных документов и шаблонов.', 'content/rubrics'),
self::item('#^/themes#', 'Темы публичного сайта', 'Управляет файлами оформления, подключениями CSS/JavaScript и способом сборки публичной страницы.', array('Файлы темы находятся в templates/<код темы> и проходят проверку пути и расширения.', 'Во вкладке «Настройки» выбирается нативная сборка AVE.cms или Twig-оболочка темы.', 'Текстовые файлы получают ревизии; тему можно экспортировать или импортировать безопасным ZIP-пакетом.'), 'Ассеты и переопределения темы работают в обоих режимах сборки.', 'content/themes'),
self::item('#^/templates#', 'Шаблоны страниц', 'Хранит внешнюю HTML/PHP-оболочку сайта, в которую подставляется содержимое документа и системные теги.', array('Шаблон №1 является системным и не удаляется.', 'Сохранение обновляет БД, файловый кеш и создаёт ревизию.', 'Палитра тегов вставляет зарегистрированные блоки, навигации и значения страницы.'), 'Ошибка PHP в основном шаблоне может нарушить весь публичный сайт — используйте проверку перед сохранением.', 'content/templates'),
self::item('#^/blocks#', 'Блоки', 'Единый реестр HTML-фрагментов, текста и исполняемой логики для шаблонов и документов.', array('Редактор выбирается отдельно для каждого блока: PHP, HTML, rich text или обычный текст.', 'Основной публичный вызов использует тег sysblock; старый тег block поддерживается как синоним.', 'Группы организуют список и не влияют на публичный вывод.'), 'PHP включайте только для блоков, которым действительно нужна серверная логика.', 'content/blocks'),
self::item('#^/navigation#', 'Навигация', 'Создаёт меню сайта: шаблоны уровней, пункты, вложенность, условия показа и связи с документами.', array('Каждая навигация имеет собственный тег для вставки в шаблон.', 'Пункты можно сортировать и переносить между уровнями.', 'Связанный документ позволяет не вводить URL вручную.'), 'После изменения структуры очищается кеш соответствующей навигации.', 'content/navigation'),
self::item('#^/requests#', 'Запросы', 'Формирует выборки документов по рубрикам и полям, а затем выводит их через шаблоны элементов и списка.', array('Условия определяют, какие документы попадут в результат.', 'Основной и постраничный шаблоны отвечают только за представление.', 'External/AJAX-флаги разрешают прямой вызов запроса и требуют осторожности.'), 'Проверяйте запрос после изменения условий: он может использоваться сразу в нескольких страницах.', 'content/requests'),
self::item('#^/media#', 'Медиа и файлы', 'Управляет файлами в uploads: загрузкой, папками, изображениями, превью и конвертацией WebP.', array('Сетка и список показывают одни и те же физические файлы.', 'Редактор изображений создаёт производные файлы для crop и resize.', 'При удалении исходника удаляются его превью и связанная WebP-копия.'), 'Не размещайте служебные PHP-файлы в uploads; раздел предназначен только для пользовательских медиа.', 'media'),
self::item('#^/system/orders#', 'Заказы и корзина', 'Обрабатывает заказы, состояния оплаты и доставки, шаблоны корзины и брошенные сессии покупателей.', array('Карточка заказа фиксирует товары, цены и выбранные варианты на момент оформления.', 'Способы оплаты настраиваются во вкладке «Оплата», реквизиты провайдеров — во вкладке «Gateways».', 'Брошенные корзины очищаются по сроку хранения и доступны для диагностики.'), 'Отмена и возврат могут влиять на платёжный шлюз — проверяйте итоговый статус операции.'),
self::item('#^/system/customers#', 'Пользователи сайта', 'Управляет публичными аккаунтами, регистрацией, OAuth-гейтами и полями профиля.', array('Email-регистрация, восстановление доступа и создание аккаунта после заказа входят в ядро.', 'Дополнительные поля независимо подключаются к регистрации и личному кабинету.', 'Подключённые OAuth-гейты открывают ту же публичную сессию, что и обычная форма входа.', 'Отключение аккаунта запрещает вход, но сохраняет связанные данные.'), 'Email остаётся основным идентификатором; дополнительные способы входа поставляются отдельными модулями.'),
self::item('#^/content/contacts/forms/\d+$#', 'Конструктор формы', 'Настраивает поля, шаблон формы, получателей, письмо и поведение после отправки.', array('Теги полей доступны в шаблоне формы и письма.', 'Получатели задаются по одному на строку.', 'История обращений хранит отправленные значения и ответы менеджеров.'), 'После изменения шаблона отправьте тестовое обращение и проверьте письмо.'),
self::item('#^/content/contacts#', 'Формы и обращения', 'Управляет публичными формами обратной связи и журналом сообщений посетителей.', array('Форма состоит из полей, шаблонов и почтовых настроек.', 'История позволяет менять статус обращения и отвечать посетителю.', 'Копирование формы создаёт независимый набор настроек.'), 'Проверяйте адреса получателей и настройки системной почты перед публикацией формы.'),
self::item('#^/content/rss#', 'RSS-каналы', 'Публикует документы выбранной рубрики в XML-ленте по настраиваемому адресу /rss/{alias}.', array('Заголовок и описание можно брать из полей рубрики.', 'Количество элементов ограничивает размер выдаваемой ленты.', 'Префикс /rss/ зарезервирован и не может использоваться документами.'), 'После изменения алиаса обновите внешние подписки и проверьте XML из списка.'),
self::item('#^/content/feeds#', 'Товарные фиды', 'Формирует управляемые XML/YML-выгрузки товаров для партнеров и торговых площадок.', array('Категории определяют товарную выборку, исключения применяются после дочерних разделов.', 'Поля XML и характеристики связываются с индексом каталога или полями товарной рубрики.', 'Предпросмотр не меняет кеш, а пересоздание атомарно публикует новый файл.'), 'Не меняйте закрепленные legacy URL у действующих партнеров; редактируйте связанную конфигурацию фида.'),
self::item('#^/content/price-import#', 'Импорт цен', 'Обновляет цены товаров из XLSX после обязательного предварительного просмотра изменений.', array('Сначала загрузите файл или получите его по настроенной HTTPS-ссылке.', 'Выберите лист и сформируйте предварительный просмотр.', 'Применение изменяет только подтверждённые строки и записывает результат в историю.'), 'Не применяйте импорт, если предварительный просмотр показывает неожиданные товары или пустые цены.'),
self::item('#^/users#', 'Пользователи', 'Управляет сотрудниками, которые могут входить в панель управления, их ролями и активностью.', array('Роль определяет доступные разделы и действия.', 'Отключённый пользователь не может войти, но сохраняется в журнале действий.', 'Нельзя отключить или удалить собственную активную учётную запись.'), 'Публичные аккаунты находятся в отдельном разделе «Пользователи сайта».', 'administration/users'),
self::item('#^/roles#', 'Роли и права', 'Определяет набор разрешений для администраторов и разграничивает доступ к функциям системы.', array('Права сгруппированы по модулям и назначаются роли целиком.', 'Системная роль admin всегда имеет полный доступ.', 'Роль с назначенными пользователями нельзя удалить.'), 'После изменения роли права применяются при следующей проверке доступа пользователя.'),
self::item('#^/settings/main$#', 'Основные настройки', 'Управляет публичным доступом, параметрами сайта, почты, документов и вывода.', array('Режим разработки возвращает посетителям временную страницу 503 и закрывает динамические маршруты от индексации.', 'Сотрудник с отдельным правом продолжает видеть документы, модули и API.', 'Поля сгруппированы по назначению, а тип значения проверяется перед сохранением.', 'Секреты устанавливаемых модулей не должны храниться в общих настройках.'), 'Перед открытием сайта переключите публичный доступ и проверьте страницу в отдельном браузере без авторизации.'),
self::item('#^/settings/interface$#', 'Настройка интерфейса', 'Определяет порядок и видимость пунктов левого меню и виджетов дашборда.', array('Перетаскивание меняет порядок только для панели управления.', 'Скрытие пункта не отменяет право роли и не блокирует прямой URL.', 'Виджеты установленных модулей появляются в списке автоматически.'), 'Доступ к функциям всегда ограничивайте через роли и права, а не только через видимость меню.'),
self::item('#^/settings/security$#', 'Подтверждение критических действий', 'Управляет повторным запросом пароля перед изменением исполняемого кода и установкой модулей.', array('Подтверждение действует пять минут для текущего пользователя и IP-адреса.', 'Отключение повторного пароля не отменяет проверку прав роли и CSRF-токена.', 'Настройка распространяется на блоки, шаблоны, рубрики, запросы, навигацию и другие операции с кодом.'), 'По умолчанию подтверждение выключено; на рабочем сайте его можно включить как дополнительный защитный слой.'),
self::item('#^/settings/constants$#', 'Системные константы', 'Хранит типизированные значения конфигурации, доступные ядру и публичному сайту.', array('Тип определяет допустимый редактор и формат сохранённого значения.', 'Перед добавлением проверьте, нет ли уже настройки с тем же назначением.', 'Удаляйте константу только после поиска её использования в коде и шаблонах.'), 'Название константы является программным контрактом и не должно меняться без миграции.'),
self::item('#^/settings/paginations$#', 'Шаблоны пагинации', 'Настраивает HTML постраничной навигации для документов и сохранённых запросов.', array('Базовый шаблон защищён от удаления.', 'Теги страниц, текущего состояния и ссылок подставляются во время рендера.', 'После изменения проверьте первую, среднюю и последнюю страницы списка.'), 'Шаблон должен сохранять доступность ссылок и понятное обозначение текущей страницы.'),
self::item('#^/settings/maintenance$#', 'Обслуживание', 'Очищает адресные кеши и накопленные служебные данные AVE.cms.', array('Каждая кнопка работает только со своим источником данных.', 'Очистка кеша не удаляет документы и настройки.', 'Ревизии и статистика просмотров удаляются необратимо.'), 'Перед очисткой данных, которые нельзя восстановить из документов, создайте резервную копию.'),
self::item('#^/settings/diagnostics$#', 'Диагностика системы', 'Проверяет PHP, расширения, доступ к каталогам, почту, данные модулей и готовность публичной части.', array('Проверки не отправляют письма и не выполняют внешние операции.', 'Красные состояния нужно устранить до публикации.', 'Тяжёлые проверки могут кратковременно кешироваться.'), 'Диагностика ядра не заменяет отдельную проверку подключённых модулей и внешних сервисов.'),
self::item('#^/settings/files$#', 'Системные файлы', 'Даёт контролируемый редактор разрешённых PHP-файлов конфигурации и пользовательских функций.', array('Список файлов ограничен системой и не является файловым менеджером.', 'Перед сохранением выполняется проверка PHP-синтаксиса.', 'Ошибка в таком файле может повлиять и на панель, и на публичный сайт.'), 'Используйте этот раздел для небольших проектных дополнений; крупную функциональность оформляйте модулем.'),
self::item('#^/settings/benchmark$#', 'Производительность', 'Измеряет скорость PHP, базы данных, файловых операций и кеша на текущем сервере.', array('Результаты нужны для сравнения одного окружения во времени, а не разных проектов.', 'Повторяйте тест после обновления PHP, БД или инфраструктуры.', 'Тест создаёт кратковременную нагрузку и сохраняет историю запусков.'), 'Оценивайте не только общий балл, но и конкретный медленный этап.'),
self::item('#^/settings#', 'Настройки системы', 'Собирает общие параметры AVE.cms, интерфейс, константы, пагинацию, обслуживание и диагностику.', array('Каждый подраздел имеет собственный URL и назначение.', 'Типизированные значения проверяются перед сохранением.', 'Настройки устанавливаемых модулей хранятся вместе с соответствующим модулем.'), 'Меняйте только параметры, назначение которых понятно и проверено на текущем сайте.', 'administration/settings'),
self::item('#^/events#', 'Системные события', 'Объединяет аудит действий, ошибки, SQL-журнал, 404 и статистику внешних переходов.', array('Цвет и состояние помогают отличать успешные операции, предупреждения и ошибки.', 'Переходы группируются по посетителю, странице входа и источнику; открытые IP и произвольные query-параметры не сохраняются.', 'Фильтры и CSV-экспорт работают отдельно для каждого источника.'), 'Очистка журнала необратима и не исправляет причину ошибки.'),
self::item('#^/security/ip-blocks#', 'IP-блокировки', 'Управляет ручными блокировками адресов, а автоматические ограничения выполняет RateLimiter.', array('Блокировка может быть постоянной или иметь дату окончания.', 'Причина видна администраторам и записывается в аудит.', 'Разблокировка возвращает доступ сразу.'), 'Не блокируйте адрес reverse proxy без проверки реального клиентского IP.'),
self::item('#^/database#', 'База данных', 'Показывает рабочие таблицы AVE.cms, выполняет обслуживание, применяет миграции ядра и управляет полными резервными копиями.', array('OPTIMIZE применяется только к выбранной таблице или ко всей рабочей схеме после подтверждения.', 'Применение миграций не восстанавливает данные: это отдельная операция развития структуры ядра.', 'Новые дампы используют {{prefix}}, поэтому могут восстанавливаться с другим dbpref.', 'Перед восстановлением система проверяет файл и автоматически создаёт страховочную копию.'), 'Восстановление полностью заменяет таблицы текущей установки; рабочую копию дополнительно храните вне сервера.', 'database'),
self::item('#^/kanban$#', 'Канбан', 'Организует личные рабочие карточки по колонкам и показывает доску на дашборде.', array('Карточки и порядок сохраняются без перезагрузки.', 'Колонки можно настроить под собственный процесс.', 'Каждый сотрудник работает со своей доской.'), 'Не используйте канбан как журнал системных ошибок: для них предназначены события и Todo.'),
self::item('#^/notes$#', 'Заметки', 'Хранит личные текстовые заметки сотрудника и закрепляет важные записи на дашборде.', array('Заметки видны только их владельцу.', 'Закрепление влияет на порядок выдачи.', 'Быстрое создание доступно из шапки, если вклад модуля включён.'), 'Не храните в заметках пароли, токены и другие секреты.'),
self::item('#^/reminders$#', 'Напоминания', 'Хранит личные задачи с датой и выводит просроченные пункты в общем колокольчике.', array('Срок определяет состояние напоминания.', 'Выполнение сохраняется сразу.', 'Просроченный счётчик формируется отдельно для текущего сотрудника.'), 'Удаление напоминания необратимо; выполненный пункт можно вернуть в работу.'),
self::item('#^/notfound$#', 'Ошибки 404', 'Показывает несуществующие публичные адреса и помогает создать документ или редирект.', array('Сначала проверьте источник переходов и частоту запроса.', 'Для старого адреса существующей страницы создайте постоянный редирект.', 'Случайные сканирующие URL можно удалить из журнала без создания страницы.'), 'Редирект должен вести на действительно соответствующий документ, а не всегда на главную.'),
self::item('#^/seo-audit$#', 'SEO-аудит', 'Проверяет документы на технические и редакционные проблемы, влияющие на поисковое представление.', array('Фильтры помогают сначала разобрать критичные и массовые проблемы.', 'Оценка документа складывается из нескольких независимых проверок.', 'Исправления выполняются в редакторе документа и проявляются после повторного аудита.'), 'Автоматическая оценка является подсказкой и не заменяет проверку содержания страницы.', 'modules/seo-audit'),
self::item('#^/todo$#', 'Todo', 'Хранит личный список замечаний и следующих действий для текущего администратора.', array('Задачу можно создать из выпадающего списка в шапке на любой странице панели управления.', 'Чекбокс сразу сохраняет состояние без перезагрузки.', 'Каждый пользователь видит только собственные задачи.'), 'Удаление задачи необратимо; выполненные пункты можно вернуть в работу обычным чекбоксом.'),
self::item('#^/modules/search#', 'Поиск по сайту', 'Индексирует опубликованные документы и формирует обычную страницу результатов и JSON для живого поиска.', array('Области позволяют отдельно искать по товарам, новостям, статьям и другим наборам рубрик.', 'Алгоритм определяет разбор строки и вес точного совпадения, заголовка и содержимого.', 'Штатный HTML можно заменить шаблонами модуля или существующим запросом документов.'), 'После изменения рубрик или индексируемых полей выполните полную переиндексацию.', 'modules/search'),
self::item('#^/modules/commerceml#', 'Обмен с 1С', 'Принимает CommerceML по защищённому адресу и сопоставляет товары 1С с документами товарной рубрики.', array('Для каждой интеграции создайте отдельный профиль с логином, паролем и рубрикой.', 'Поля внешнего ID, артикула, цены и остатка выбираются из полей назначенной рубрики.', 'Пробный XML можно загрузить из списка профилей и проверить результат в журнале обменов.'), 'Создание неизвестных товаров выключено по умолчанию: сначала проверьте сопоставление на существующих документах.'),
self::item('#^/modules/experiments#', 'A/B-тесты', 'Распределяет посетителей между вариантами и собирает показы и конверсии без смешивания вариантов в общем кеше.', array('Код эксперимента используется в шаблонном теге, API и атрибутах отслеживания конверсии.', 'Вес вариантов задаёт распределение внутри выбранного охвата аудитории.', 'Черновик не участвует в публичном выводе; перед запуском проверьте каждый вариант.'), 'Изменение цены штатным вариантом влияет только на представление и не меняет сумму заказа.'),
self::item('#^/modules/popups#', 'Всплывающие окна', 'Управляет кампаниями поп-апов, условиями показа, A/B-вариантами и собранными заявками.', array('Сценарий определяет момент показа: задержку, прокрутку или попытку ухода.', 'Пути включения и исключения принимают по одному шаблону на строку; символ * заменяет произвольную часть.', 'HTML вариантов контролируется проектом, а заявки обрабатываются на отдельной вкладке.'), 'Проверьте кампанию на тестовом URL и мобильном экране до включения автоматической вставки.'),
self::item('#^/modules/reviews#', 'Отзывы', 'Собирает оценки и отзывы к документам, позволяет модерировать публикацию и отвечать от имени компании.', array('Состояние определяет, попадёт ли отзыв в публичный список.', 'Признак подтверждённой покупки устанавливается только после проверки заказа.', 'Ответ компании сохраняется вместе с отзывом и выводится его публичным шаблоном.'), 'Удаление отзыва необратимо; спорный материал безопаснее сначала отклонить.', 'modules/reviews'),
self::item('#^/modules/scheduler#', 'Планировщик', 'Собирает фоновые задачи установленных модулей в единый реестр, запускает их по расписанию и хранит журнал.', array('Защищённый URL добавляется в планировщик хостинга с интервалом в одну минуту.', 'Cron-выражение и активность каждой задачи меняются прямо в таблице.', 'Ручной запуск использует тот же обработчик и попадает в общий журнал.'), 'После смены секретного URL обновите задание на хостинге: старый адрес сразу перестанет работать.'),
self::item('#^/modules/search-analytics#', 'Поисковые запросы', 'Показывает реальные поисковые фразы посетителей, частоту запросов и случаи пустой выдачи.', array('Фильтр по области разделяет спрос по новостям, товарам, статьям и другим настроенным областям.', 'Состояние и заметка позволяют отмечать разобранные запросы и планировать новый контент.', 'Показатель «Результатов сейчас» помогает проверить, исправлена ли нулевая выдача.'), 'Очистка истории удаляет аналитические данные, но не затрагивает поисковый индекс.'),
self::item('#^/modules/system-health#', 'Мониторинг здоровья', 'Проверяет доступность базы данных, диска, фоновых задач и заданных внешних HTTPS-сервисов.', array('Текущий снимок показывает независимый результат каждой инфраструктурной проверки.', 'История помогает отличить разовый сбой от повторяющейся проблемы.', 'Для внешнего сервиса задаются ожидаемый HTTP-статус и ограниченный таймаут.'), 'Мониторинг фиксирует рабочее состояние, но не заменяет приёмочный чек-лист перед запуском сайта.'),
self::item('#^/modules/traffic-analytics#', 'Источники трафика', 'Группирует входящие переходы по UTM-меткам, источникам, кампаниям и посадочным страницам.', array('Период и фильтры одинаково применяются к сводке, таблицам и CSV-экспорту.', 'UTM-конструктор собирает ссылку в браузере и не сохраняет введённые значения.', 'Один вход посетителя на ту же страницу за день агрегируется без хранения IP.'), 'Перед очисткой выбранного периода выгрузите CSV, если данные нужны для отчётности.'),
self::item('#^/modules/antispam#', 'Антиспам', 'Настраивает профили защиты публичных форм и показывает журнал отклонённых отправок.', array('Профиль объединяет лимиты, проверку скрытого поля и анализ содержимого для конкретной формы.', 'Журнал объясняет, какое правило остановило запрос, и помогает скорректировать чувствительность.', 'Доверенные исключения применяйте только к контролируемым интеграциям.'), 'Не ослабляйте общий профиль из-за одного ложного срабатывания: создайте отдельный профиль для нужной формы.'),
self::item('#^/modules/interactions#', 'Взаимодействия', 'Хранит общий журнал оценок, реакций и голосов, который используют прикладные модули.', array('Канал определяет допустимые действия и типы объектов.', 'Агрегаты ускоряют публичный вывод счётчиков и рейтингов.', 'Пересчёт восстанавливает агрегаты из исходного журнала действий.'), 'Обычно этот раздел не вставляется в шаблон напрямую: публичный интерфейс предоставляют рейтинги, опросы и комментарии.'),
self::item('#^/modules/ratings#', 'Рейтинги', 'Добавляет на документы и другие разрешённые объекты настраиваемую шкалу оценок.', array('Документы включают страницы, товары, новости и статьи, построенные на рубриках.', 'Тег [mod_rating] использует текущий документ; явная цель задаётся типом и ID.', 'Кнопки тегов вставляют доступные значения в HTML-шаблон рейтинга.'), 'Голоса и итоговые значения хранятся в модуле «Взаимодействия».'),
self::item('#^/modules/comments#', 'Комментарии', 'Управляет древовидными обсуждениями, премодерацией, антиспамом и публичными шаблонами.', array('Разрешите только те типы объектов, на которых действительно нужны обсуждения.', 'Шаблоны списка, комментария и формы имеют собственные наборы тегов.', 'Публикация, скрытие и отметка спама выполняются из общего журнала.'), 'Оценка комментариев использует отдельный канал модуля «Взаимодействия».'),
self::item('#^/modules/polls#', 'Опросы', 'Создаёт одиночные и множественные голосования с расписанием, доступом и управляемыми результатами.', array('Для публикации вставьте [mod_poll:ID] в документ, блок или шаблон.', 'Архив опросов доступен по настраиваемому публичному URL.', 'Шаблоны опроса, варианта, результата и архива имеют разные наборы тегов.'), 'Удаление опроса также удаляет варианты ответа и собранные голоса.'),
self::item('#^/modules/galleries#', 'Галереи', 'Собирает изображения медиатеки в упорядоченные сетки и слайдеры с собственными публичными URL.', array('Файлы остаются в медиатеке; галерея хранит только связи, подписи и порядок.', 'Тег [mod_gallery:ID] выводит коллекцию в документе, блоке или шаблоне.', 'Отдельные шаблоны управляют оболочкой галереи и разметкой одного изображения.'), 'Удаление галереи не удаляет исходные файлы и созданные для них превью.'),
self::item('#^/modules/banners#', 'Баннеры', 'Управляет рекламными местами, ротацией изображений, расписанием и внутренней статистикой переходов.', array('Стабильный код места используется в теге [mod_banner:КОД].', 'Вес влияет только на стратегию «По весу»; равномерная ротация выбирает материал с меньшим числом показов.', 'Изображения выбираются из общей медиатеки и не удаляются вместе с баннером.'), 'Нулевой лимит означает отсутствие ограничения; сброс статистики необратим.'),
self::item('#^/modules/faq#', 'Вопросы и ответы', 'Собирает частые вопросы в независимые коллекции с публичной страницей, тегом, API и Schema.org.', array('Стабильный код используется в теге [mod_faq:КОД] и публичном URL.', 'Ответ оформляется в rich-text редакторе, а вопросы сортируются перетаскиванием.', 'JSON-LD включает только опубликованные вопросы опубликованной коллекции.'), 'После изменения базового URL обновите ссылки в шаблонах и проверьте, что новый адрес не занят документом.'),
self::item('#^/modules/related#', 'Похожие материалы', 'Подбирает связанные документы по смыслу, ручным связям или стабильному кольцевому порядку.', array('Профиль задаёт рубрики, признаки, веса, лимит и способ вывода.', 'Встроенный шаблон подходит для новой разметки, существующий запрос — для готовых карточек сайта.', 'Тег [mod_related:КОД] использует текущий документ, а API принимает его ID явно.'), 'После установки и массового изменения контента перестройте индекс на отдельной вкладке.'),
self::item('#^/modules/demo-site#', 'Демо-сайт', 'Устанавливает и обслуживает демонстрационный набор рубрик, документов, навигации и шаблонов.', array('Предпросмотр показывает состав пакета до изменения данных.', 'Повторное применение использует ledger пакета и не должно создавать дубли.', 'Удаление затрагивает только сущности, созданные этим пакетом.'), 'Перед применением к сайту с собственным контентом сделайте резервную копию базы и публичных шаблонов.'),
self::item('#^/modules/site-readiness#', 'Готовность сайта', 'Проверяет базовые настройки, документы, шаблоны, модули и окружение перед публикацией сайта.', array('Профиль определяет глубину и набор проверяемых подсистем.', 'Красные результаты требуют исправления, предупреждения нужно оценивать в контексте проекта.', 'Проверка ничего не исправляет и не меняет публичные данные.'), 'Запускайте полный профиль после изменения структуры сайта, модулей или серверного окружения.'),
self::item('#^/modules/vk-oauth#', 'Вход через VK ID', 'Подключает VK ID как дополнительный способ входа в существующий публичный аккаунт.', array('Redirect URL из настроек должен точно совпадать с адресом в приложении VK.', 'Внешний профиль связывается с общим аккаунтом сайта, а не создаёт отдельную сессию.', 'Секрет приложения хранится только в настройках gateway.'), 'Проверяйте вход и привязку на тестовом аккаунте после смены домена или ключей приложения.'),
self::item('#^/modules/yandex-oauth#', 'Вход через Яндекс', 'Подключает Яндекс ID как дополнительный способ входа в существующий публичный аккаунт.', array('Callback URL из настроек должен точно совпадать с адресом OAuth-приложения.', 'Полученный профиль связывается с общим аккаунтом сайта.', 'Client secret не должен попадать в публичный шаблон или журнал.'), 'После смены домена обновите callback в панели Яндекса и настройках модуля.'),
self::item('#^/modules/smsc-auth#', 'Вход по SMS', 'Подключает SMSC для регистрации и беспарольного входа по подтверждённому номеру телефона.', array('API-ключ хранится в закрытом хранилище секретов.', 'Код одноразовый, ограничен по времени и числу попыток.', 'Телефон относится к общей учётной записи, поэтому отдельный профиль для SMS не создаётся.'), 'Перед включением на сайте отправьте тестовое SMS и проверьте вход существующего и нового пользователя.', 'modules/smsc-auth'),
self::item('#^/modules/yookassa#', 'YooKassa', 'Настраивает платёжный gateway YooKassa для оформления и контроля оплаты заказов.', array('Идентификатор магазина и секрет применяются только серверной частью.', 'Callback принимает изменения статуса от платёжной системы.', 'Тестовый и рабочий режимы должны использовать соответствующие ключи.'), 'Перед рабочим запуском проведите полный тест оплаты, отмены и повторного callback.'),
self::item('#^/modules/psb#', 'ПСБ / СФР', 'Настраивает отдельный платёжный сценарий для товаров, доступных к оплате электронным сертификатом.', array('Признак доступности и код ТСР задаются в данных товара.', 'Gateway показывается только для подходящего состава заказа.', 'Ключи и callback используются только серверной частью.'), 'Проверьте смешанную корзину и отказ платежа до включения способа на публичном сайте.'),
self::item('#^/modules/file-security#', 'Безопасность файлов', 'Проверяет целостность исходников по SHA-256, ищет подозрительные конструкции и защищает загрузки.', array('Сначала сформируйте эталон на заведомо проверенной версии проекта.', 'Находка эвристики является причиной проверить файл, а не доказательством заражения.', 'В карантин автоматически можно переместить только загрузки и временные вложения; системные файлы исправляются вручную.'), 'Не принимайте изменённый файл в эталон, пока не проверили причину изменения и его содержимое.'),
self::item('#^/modules#', 'Модули', 'Устанавливает и обновляет расширения сайта, их таблицы, шаблоны и состояние подключения.', array('Системные функции не отображаются как устанавливаемые модули.', 'Update применяет предусмотренную модулем схему обновления.', 'Uninstall может удалить данные модуля, если это заложено его manifest.'), 'Проверяйте совместимость версии и резервную копию перед обновлением модуля.', 'modules'),
self::item('#^/system/document-jobs#', 'Операции с документами', 'Запускает управляемые пакетные задания над документами и хранит подробный журнал выполнения.', array('Сначала проверьте PHP-синтаксис задания.', 'Runner выполняет работу пошагово с lock и отображает прогресс.', 'Остановленное задание можно проанализировать по журналу.'), 'Большие задания запускайте небольшими пакетами и контролируйте ошибки после каждого шага.'),
self::item('#^/system/console#', 'PHP-консоль', 'Выполняет административные PHP-сниппеты в ограниченном дочернем процессе для диагностики и обслуживания.', array('Сниппеты можно сохранять для повторного использования.', 'Перед запуском выполняется проверка синтаксиса.', 'Время, память и размер вывода ограничены.'), 'Консоль выполняет код с доступом к системе — используйте её только для проверенных операций.', 'administration/console'),
self::item('#^/system/updates#', 'Обновления AVE.cms', 'Устанавливает подписанные патчи ядра с проверкой локальных изменений, резервной копией и откатом.', array('Источник использует отдельный публичный RSA-ключ владельца AVE.cms.', 'Подготовка патча не меняет файлы до отдельного подтверждения установки.', 'Незавершённое задание продолжается с сохранённого этапа после повторного открытия раздела.'), 'Не удаляйте storage/updates и не копируйте полную сборку поверх сайта во время активного обновления.', 'administration/updates'),
self::item('#^/(?:dashboard)?$#', 'Дашборд', 'Показывает общее состояние AVE.cms, последние документы и виджеты установленных модулей.', array('Состав и порядок виджетов меняются в настройках интерфейса.', 'Последние документы и системные показатели дают быстрый переход в профильный раздел.', 'Модульные виджеты появляются только у пользователей с соответствующим правом.'), 'Используйте дашборд как отправную точку, а подробные данные смотрите в соответствующих разделах.', 'administration/dashboard'),
);
}
protected static function item($pattern, $title, $description, array $items, $note, $doc = '')
{
return array(
'pattern' => $pattern,
'title' => AdminLocale::translateMarkup($title),
'description' => AdminLocale::translateMarkup($description),
'items' => array_map(array(AdminLocale::class, 'translateMarkup'), $items),
'note' => AdminLocale::translateMarkup($note),
'doc' => (string) $doc,
);
}
}
+154
View File
@@ -0,0 +1,154 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/SensitiveRoutes.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use InvalidArgumentException;
/** Metadata contract and runtime inventory for high-risk Adminx routes. */
class SensitiveRoutes
{
protected static $operations = array(
'module.code' => 'Установка или изменение исполняемого кода модуля',
'core.update' => 'Установка подписанного обновления ядра',
'stored_php.execute' => 'Выполнение сохранённого PHP-кода',
'stored_php.write' => 'Сохранение PHP-кода, исполняемого runtime',
'theme_assets.write' => 'Изменение исполняемых CSS/JavaScript и файлов публичной темы',
'legacy_migration.write' => 'Изменение реквизитов исходной системы',
'legacy_migration.execute' => 'Очистка стартовых данных и миграция старой AVE.cms',
'database.restore' => 'Полная замена рабочей схемы данными из резервной копии',
);
protected static $required = array(
'POST /modules/archive/install' => array('module.code', 'install_module_code'),
'POST /modules/lifecycle/{code}/install' => array('module.code', 'install_module_code'),
'POST /modules/lifecycle/{code}/update' => array('module.code', 'install_module_code'),
'POST /modules/lifecycle/{code}/reinstall' => array('module.code', 'install_module_code'),
'POST /modules/lifecycle/{code}/repair' => array('module.code', 'install_module_code'),
'POST /modules/lifecycle/{code}/uninstall' => array('module.code', 'install_module_code'),
'POST /modules/lifecycle/{code}/remove' => array('module.code', 'install_module_code'),
'POST /system/updates/settings' => array('core.update', 'install_core_updates'),
'POST /system/updates/upload' => array('core.update', 'install_core_updates'),
'POST /system/updates/download/{id}' => array('core.update', 'install_core_updates'),
'POST /system/updates/jobs/{id}/step' => array('core.update', 'install_core_updates'),
'POST /system/updates/jobs/{id}/rollback' => array('core.update', 'install_core_updates'),
'POST /system/console/execute' => array('stored_php.execute', 'execute_console'),
'POST /blocks' => array('stored_php.write', 'manage_blocks'),
'POST /blocks/{id}' => array('stored_php.write', 'manage_blocks'),
'POST /blocks/{id}/copy' => array('stored_php.write', 'manage_blocks'),
'POST /blocks/revisions/{revision}/restore' => array('stored_php.write', 'manage_blocks'),
'POST /templates' => array('stored_php.write', 'manage_templates'),
'POST /templates/{id}' => array('stored_php.write', 'manage_templates'),
'POST /templates/{id}/copy' => array('stored_php.write', 'manage_templates'),
'POST /templates/revisions/{revision}/restore' => array('stored_php.write', 'manage_templates'),
'POST /rubrics' => array('stored_php.write', 'manage_rubrics'),
'POST /rubrics/{id}' => array('stored_php.write', 'manage_rubrics'),
'POST /rubrics/{id}/templates/main' => array('stored_php.write', 'manage_rubrics'),
'POST /rubrics/{id}/templates' => array('stored_php.write', 'manage_rubrics'),
'POST /rubrics/templates/{template}' => array('stored_php.write', 'manage_rubrics'),
'POST /requests' => array('stored_php.write', 'manage_requests'),
'POST /requests/{id}' => array('stored_php.write', 'manage_requests'),
'POST /requests/{id}/copy' => array('stored_php.write', 'manage_requests'),
'POST /requests/{id}/conditions' => array('stored_php.write', 'manage_requests'),
'POST /navigation' => array('stored_php.write', 'manage_navigation'),
'POST /navigation/{id}' => array('stored_php.write', 'manage_navigation'),
'POST /navigation/{id}/copy' => array('stored_php.write', 'manage_navigation'),
'POST /modules/content-packages/import' => array('stored_php.write', 'import_content_packages'),
'POST /system/document-jobs' => array('stored_php.write', 'manage_document_jobs'),
'POST /system/document-jobs/{id}' => array('stored_php.write', 'manage_document_jobs'),
'POST /system/document-jobs/{id}/start' => array('stored_php.execute', 'execute_document_jobs'),
'POST /system/document-jobs/{id}/step' => array('stored_php.execute', 'execute_document_jobs'),
'POST /themes/file' => array('theme_assets.write', 'manage_themes'),
'POST /themes/files/create' => array('theme_assets.write', 'manage_themes'),
'POST /themes/upload' => array('theme_assets.write', 'manage_themes'),
'POST /themes/path/delete' => array('theme_assets.write', 'manage_themes'),
'POST /themes/manifest' => array('theme_assets.write', 'manage_themes'),
'POST /themes/create' => array('theme_assets.write', 'manage_themes'),
'POST /themes/activate' => array('theme_assets.write', 'manage_themes'),
'POST /themes/import' => array('theme_assets.write', 'manage_themes'),
'POST /themes/delete' => array('theme_assets.write', 'manage_themes'),
'POST /themes/revisions/{id}/restore' => array('theme_assets.write', 'manage_themes'),
'POST /modules/legacy-migration/profiles' => array('legacy_migration.write', 'manage_legacy_migration'),
'POST /modules/legacy-migration/profiles/{id}/delete' => array('legacy_migration.write', 'manage_legacy_migration'),
'POST /modules/legacy-migration/runs/{id}/start' => array('legacy_migration.execute', 'run_legacy_migration'),
'POST /modules/legacy-migration/runs/{id}/rollback' => array('legacy_migration.execute', 'run_legacy_migration'),
'POST /modules/legacy-migration/runs/{id}/files' => array('legacy_migration.execute', 'run_legacy_migration'),
'POST /database/backup/restore' => array('database.restore', 'manage_database'),
);
public static function validate(array $route)
{
$options = isset($route['options']) && is_array($route['options']) ? $route['options'] : array();
$operation = isset($options['sensitive']) ? trim((string) $options['sensitive']) : '';
$key = self::routeKey($route);
$required = isset(self::$required[$key]) ? self::$required[$key] : null;
if ($operation === '' && !$required) {
return;
}
if ($required && ($operation !== $required[0]
|| !isset($options['permission'])
|| (string) $options['permission'] !== $required[1])) {
throw new InvalidArgumentException('Нарушена политика чувствительного маршрута ' . $key);
}
if (!isset(self::$operations[$operation])) {
throw new InvalidArgumentException('Неизвестная чувствительная операция: ' . $operation);
}
$method = isset($route['method']) ? strtoupper((string) $route['method']) : '';
if (in_array($method, array('GET', 'HEAD', 'OPTIONS'), true)) {
throw new InvalidArgumentException('Чувствительный маршрут не может использовать безопасный HTTP-метод');
}
if (empty($options['permission']) || empty($options['reauth'])) {
throw new InvalidArgumentException('Чувствительный маршрут обязан объявить permission и reauth');
}
}
public static function inventory(array $routes)
{
$items = array();
foreach ($routes as $route) {
self::validate($route);
$options = isset($route['options']) && is_array($route['options']) ? $route['options'] : array();
if (empty($options['sensitive'])) {
continue;
}
$items[] = array(
'method' => isset($route['method']) ? (string) $route['method'] : '',
'pattern' => isset($route['pattern']) ? (string) $route['pattern'] : '',
'operation' => (string) $options['sensitive'],
'permission' => (string) $options['permission'],
'reauth' => $options['reauth'],
);
}
return $items;
}
public static function required()
{
return self::$required;
}
protected static function routeKey(array $route)
{
return strtoupper(isset($route['method']) ? (string) $route['method'] : '')
. ' '
. (isset($route['pattern']) ? (string) $route['pattern'] : '');
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/SystemFeatures.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
class SystemFeatures
{
public static function enabled($feature)
{
$global = getenv('ADMINX_SYSTEM_TOOLS');
if ($global !== false && $global !== '' && !filter_var($global, FILTER_VALIDATE_BOOLEAN)) {
return false;
}
$key = 'ADMINX_SYSTEM_' . strtoupper(preg_replace('/[^A-Za-z0-9]+/', '_', (string) $feature));
$value = getenv($key);
return $value === false || $value === '' ? true : filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
}
+97
View File
@@ -0,0 +1,97 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/SystemHealth.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\Cache;
use App\Common\NotFoundLog;
/** Lightweight counters shared by the dashboard and topbar notifications. */
class SystemHealth
{
protected static $sqlErrors;
protected static $notFound;
public static function sqlErrorCount()
{
if (self::$sqlErrors !== null) {
return self::$sqlErrors;
}
$path = BASEPATH . '/tmp/logs/sql.csv';
self::$sqlErrors = self::countCsvIncrementally($path);
return self::$sqlErrors;
}
protected static function countCsvIncrementally($path)
{
if (!is_file($path)) {
Cache::forget('adminx.system-health.sql-log');
return 0;
}
$stat = @stat($path);
if (!is_array($stat)) { return 0; }
$state = Cache::get('adminx.system-health.sql-log', array());
$sameFile = is_array($state)
&& isset($state['device'], $state['inode'], $state['size'], $state['modified'], $state['count'])
&& (int) $state['device'] === (int) $stat['dev']
&& (int) $state['inode'] === (int) $stat['ino'];
if ($sameFile
&& (int) $state['size'] === (int) $stat['size']
&& (int) $state['modified'] === (int) $stat['mtime']) {
return (int) $state['count'];
}
$count = $sameFile && (int) $state['size'] < (int) $stat['size']
? (int) $state['count']
: 0;
$offset = $sameFile && (int) $state['size'] < (int) $stat['size']
? (int) $state['size']
: 0;
$handle = @fopen($path, 'rb');
if ($handle === false) { return $count; }
if ($offset > 0) { fseek($handle, $offset); }
while (($row = fgetcsv($handle, 0, ',')) !== false) {
if (isset($row[0]) && $row[0] !== '') { $count++; }
}
fclose($handle);
Cache::set('adminx.system-health.sql-log', array(
'device' => (int) $stat['dev'],
'inode' => (int) $stat['ino'],
'size' => (int) $stat['size'],
'modified' => (int) $stat['mtime'],
'count' => $count,
), 0);
return $count;
}
public static function notFoundStats()
{
if (self::$notFound !== null) {
return self::$notFound;
}
try {
self::$notFound = NotFoundLog::stats();
} catch (\Throwable $e) {
self::$notFound = array('urls' => 0, 'hits' => 0, 'unresolved' => 0);
}
return self::$notFound;
}
}
@@ -0,0 +1,34 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/Twig/AdminConstantExpression.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support\Twig;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use Twig\Compiler;
use Twig\Node\Expression\ConstantExpression;
/**
* Строковая константа Twig с отложенным переводом интерфейса.
*/
class AdminConstantExpression extends ConstantExpression
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('\App\Adminx\Support\AdminLocale::translateMarkup(')
->repr($this->getAttribute('value'))
->raw(')');
}
}
@@ -0,0 +1,46 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/Twig/AdminLocaleExtension.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support\Twig;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Adminx\Support\AdminLocale;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
class AdminLocaleExtension extends AbstractExtension
{
public function getNodeVisitors()
{
return array(new AdminLocaleNodeVisitor());
}
public function getFilters()
{
return array(
new TwigFilter('admin_trans', array(AdminLocale::class, 'translateMarkup')),
);
}
public function getFunctions()
{
return array(
new TwigFunction('admin_trans', array(AdminLocale::class, 'translateMarkup')),
);
}
}
@@ -0,0 +1,57 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/Twig/AdminLocaleNodeVisitor.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support\Twig;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use Twig\Environment;
use Twig\Node\Node;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\TextNode;
use Twig\NodeVisitor\NodeVisitorInterface;
class AdminLocaleNodeVisitor implements NodeVisitorInterface
{
public function enterNode(Node $node, Environment $environment): Node
{
return $node;
}
public function leaveNode(Node $node, Environment $environment): ?Node
{
if ($node instanceof TextNode && !$node instanceof AdminTextNode) {
return new AdminTextNode(
(string) $node->getAttribute('data'),
(int) $node->getTemplateLine()
);
}
if ($node instanceof ConstantExpression && !$node instanceof AdminConstantExpression) {
$value = $node->getAttribute('value');
if (is_string($value) && preg_match('/[А-Яа-яЁё]/u', $value)) {
return new AdminConstantExpression($value, (int) $node->getTemplateLine());
}
}
return $node;
}
public function getPriority()
{
return 0;
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file adminx/Support/Twig/AdminTextNode.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Adminx\Support\Twig;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use Twig\Compiler;
use Twig\Node\TextNode;
/**
* Статичный Twig-фрагмент Adminx, переводимый во время рендера.
*
* Перевод не компилируется в кеш Twig, поэтому разные администраторы могут
* использовать разные языки с одним набором скомпилированных шаблонов.
*/
class AdminTextNode extends TextNode
{
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write('echo \\App\\Adminx\\Support\\AdminLocale::translateMarkup(')
->string($this->getAttribute('data'))
->raw(");\n");
}
}
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+48
View File
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 1133.86 381.72">
<!-- Generator: Adobe Illustrator 30.0.0, SVG Export Plug-In . SVG Version: 2.1.1 Build 123) -->
<defs>
<style>
.st0 {
fill: url(#linear-gradient2);
}
.st1 {
fill: url(#linear-gradient1);
}
.st2 {
fill: url(#linear-gradient3);
}
.st3 {
fill: url(#linear-gradient);
}
</style>
<linearGradient id="linear-gradient" x1="30.83" y1="189.73" x2="1097.03" y2="189.73" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#006efc"/>
<stop offset="1" stop-color="#24d9ae"/>
</linearGradient>
<linearGradient id="linear-gradient1" x1="694.03" y1="326.5" x2="1103.03" y2="326.5" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#00c7f0"/>
<stop offset="1" stop-color="#21dca3"/>
</linearGradient>
<linearGradient id="linear-gradient2" x1="71.03" y1="326.63" x2="381.03" y2="326.63" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#034bab"/>
<stop offset=".1" stop-color="#0267bd"/>
<stop offset=".23" stop-color="#0182cf"/>
<stop offset=".36" stop-color="#0196dc"/>
<stop offset=".52" stop-color="#01a4e5"/>
<stop offset=".7" stop-color="#01adeb"/>
<stop offset="1" stop-color="#01b0ed"/>
</linearGradient>
<linearGradient id="linear-gradient3" x1="815.91" y1="129.18" x2="960.77" y2="220.79" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#024e92"/>
<stop offset="1" stop-color="#11d9c2"/>
</linearGradient>
</defs>
<path class="st3" d="M308.81,28.35c6.92,4.94,12.67,11.47,18.59,17.53,1.35,1.37,217.09,215.46,219,217.88,4.1,4.69,10.2,9.73,16.6,10.24,7.3.15,13,.13,19.02-4.24.78-.56,7.97-7.74,10.5-11.25,4.23-5.77,142.56-181.82,143.8-183.48,19.11-25.28,45.45-45.7,77.7-50.27,9.24-.72,18.53-.51,27.79-.45,2.79,0,220.26.36,255.21.45.06,7.7-3.1,17.2-3.38,18.02-7.54,21.28-21.64,39.29-41.62,49.98-9.81,4.54-19.53,7.32-30.35,7.26-.79,0-167.25.34-169.09.33-22.71-.06-42.39,4.52-59.18,20.97-5.81,6.22-10.97,12.94-16.09,19.73-2.21,2.91-73.4,93.36-78.97,100.3-8.15,10.16-54.88,70.12-57.79,73.97-17.39,23.02-36.53,42.24-66.54,46.43-22.85,1.95-42.31-4.55-60-19-2.69-2.35-218.78-216.04-219.55-216.84-7.41-7.48-13.73-12.23-24.51-12.59-1.25-.06-2.5-.12-3.79-.18-6.28.45-11.73,4.09-16,8.56-1.69,2.15-178.14,239.88-179.15,242.06-7.34-1.82-12.37-5.01-18-10-.75-.65-1.51-1.3-2.29-1.96-10.34-9.52-18.95-24.78-19.84-39.03-.65-18.7,3.28-30.93,14.56-45.88,1.25-1.68,170.94-229.73,171.65-230.56,11.26-13.64,22.71-18.1,39.91-20.14s37.09,2.68,51.79,12.17Z"/>
<path class="st1" d="M792.79,287.36c2.61,0,216.55-.1,219.08-.11,24.62-.09,31.39,2.53,46.36,9.24,19.79,8.87,34.25,29.65,40.79,50.28q4,14.76,4,19h-409c2.62-23.54,15.13-44.57,33-60,20.59-15.7,40.6-18.45,65.76-18.4Z"/>
<path class="st0" d="M132.06,287.48q1.06,0,2.13,0c.75,0,1.49,0,2.26-.01,2.52-.01,5.04-.01,7.55,0,1.8,0,150.3-.2,152.06-.21,13.63-.09,25.7,1.73,38.02,8.07q.99.51,2.01,1.02c21.4,11.44,35.54,32.74,42.9,55.39,1.42,4.87,2.08,8.94,2.03,14.02-38.91.05-299.09.25-300.73.25-.93,0-1.86,0-2.81,0-2.59-.26-4.23-.93-6.46-2.25,1.3-3.62,2.84-6.39,5.2-9.41q.97-1.25,1.95-2.53t2.1-2.69c1.46-1.89,2.92-3.79,4.38-5.69.74-.95,1.47-1.91,2.23-2.89,3.14-4.1,6.21-8.26,9.26-12.42.56-.77,1.13-1.54,1.71-2.33,2.34-3.18,4.67-6.37,7-9.56q21.02-28.7,27.2-28.77Z"/>
<path class="st2" d="M865.04,156.64c1.18,0,160.2.1,183.99.12-.63,5.56-1.59,10.44-3.62,15.69-.26.69-.53,1.38-.8,2.09-4.1,10.38-9.31,19.74-16.57,28.23q-1.02,1.23-2.05,2.49c-11.36,13.3-28.16,24.2-45.99,25.68-5.74.27-168.89-.1-192.95-.17.41-.55,45.39-59.85,46.11-60.8,8.33-10.26,19.09-13.37,31.9-13.32Z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

+888
View File
@@ -0,0 +1,888 @@
/**
* Общий слой JS новой админки (/adminx).
*
* Только переиспользуемые примитивы уровня всего приложения (ТЗ §4):
* Loader, Ajax (fetch под контракт success/error), Sidebar, Theme, Dropdown,
* Toast. Логика конкретных разделов живёт в modules/<Module>/assets/<module>.js
* и цепляется к этому же объекту (Adminx.<Module> = {...}).
*
* Без jQuery: fetch, querySelector, addEventListener, classList.
*/
(function (window, document) {
'use strict';
var Adminx = window.Adminx || {};
Adminx.i18n = window.AdminxI18n || {};
Adminx.t = function (key, fallback) {
return Object.prototype.hasOwnProperty.call(Adminx.i18n, key) ? Adminx.i18n[key] : (fallback || key);
};
Adminx.phrases = window.AdminxPhrases || {};
Adminx.phraseCache = {};
Adminx.tr = function (text) {
var translated = String(text == null ? '' : text);
if (Object.prototype.hasOwnProperty.call(Adminx.phraseCache, translated)) {
return Adminx.phraseCache[translated];
}
if (Object.prototype.hasOwnProperty.call(Adminx.phrases, translated)) {
Adminx.phraseCache[translated] = Adminx.phrases[translated];
return Adminx.phrases[translated];
}
var sourceText = translated;
Object.keys(Adminx.phrases).forEach(function (source) {
var startsWithWord = /^[\p{L}\p{N}_]/u.test(source);
var endsWithWord = /[\p{L}\p{N}_]$/u.test(source);
if (startsWithWord || endsWithWord) {
var escaped = source.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
translated = translated.replace(
new RegExp(
(startsWithWord ? '(^|[^\\p{L}\\p{N}_])' : '()') +
'(' + escaped + ')' +
(endsWithWord ? '(?=$|[^\\p{L}\\p{N}_])' : ''),
'gu'
),
function (match, prefix) { return prefix + Adminx.phrases[source]; }
);
} else {
translated = translated.split(source).join(Adminx.phrases[source]);
}
});
Adminx.phraseCache[sourceText] = translated;
return translated;
};
Adminx.base = function () {
var m = document.querySelector('meta[name="adminx-base"]');
return m ? m.getAttribute('content') : '';
};
Adminx.csrf = function () {
var m = document.querySelector('meta[name="csrf-token"]');
return m ? m.getAttribute('content') : '';
};
// ------------------------------------------------------------------ //
Adminx.Loader = {
show: function () { document.body.classList.add('is-loading'); },
hide: function () { document.body.classList.remove('is-loading'); }
};
// ------------------------------------------------------------------ //
// Повторное подтверждение пароля для операций с исполняемым кодом.
// ------------------------------------------------------------------ //
Adminx.ReAuth = {
pending: null,
confirm: function (config) {
if (this.pending) { return this.pending; }
var self = this;
this.pending = this.open(config || {}).then(function (confirmed) {
self.pending = null;
return confirmed;
}, function () {
self.pending = null;
return false;
});
return this.pending;
},
open: function (config) {
return new Promise(function (resolve) {
var wasLoading = document.body.classList.contains('is-loading');
Adminx.Loader.hide();
var overlay = document.createElement('div');
overlay.className = 'overlay open reauth-overlay';
overlay.innerHTML = '<form class="modal reauth-modal" data-reauth-form>' +
'<div class="modal-header"><span class="dialog-icon warning"><i class="ti ti-shield-lock"></i></span>' +
'<div><h3>' + Adminx.t('reauth_title', 'Подтвердите пароль') + '</h3><p class="reauth-subtitle">' + Adminx.t('reauth_subtitle', 'Защита чувствительной операции') + '</p></div>' +
'<button class="modal-close" type="button" data-reauth-cancel aria-label="' + Adminx.t('btn_close', 'Закрыть') + '"><i class="ti ti-x"></i></button></div>' +
'<div class="modal-body"><p class="reauth-reason" data-reauth-reason></p>' +
'<label class="field"><span class="field-label">' + Adminx.t('field_current_password', 'Пароль текущего пользователя') + '</span>' +
'<div class="input-wrap"><i class="ti ti-lock"></i><input class="input" type="password" name="password" autocomplete="current-password" required></div>' +
'<span class="field-error" data-reauth-error hidden></span></label>' +
'<p class="reauth-hint">' + Adminx.t('reauth_hint', 'После подтверждения действие продолжится автоматически. Повторный запрос пароля появится через несколько минут.') + '</p></div>' +
'<div class="modal-footer"><button class="btn btn-secondary" type="button" data-reauth-cancel>' + Adminx.t('btn_cancel', 'Отмена') + '</button>' +
'<button class="btn btn-primary" type="submit"><i class="ti ti-shield-check"></i> ' + Adminx.t('btn_confirm', 'Подтвердить') + '</button></div></form>';
var form = overlay.querySelector('[data-reauth-form]');
var input = form.elements.password;
var submit = form.querySelector('[type="submit"]');
var error = form.querySelector('[data-reauth-error]');
overlay.querySelector('[data-reauth-reason]').textContent = config.reason || Adminx.t('reauth_reason', 'Это действие изменяет исполняемый код системы.');
var finish = function (confirmed) {
document.removeEventListener('keydown', onKeydown);
overlay.remove();
if (wasLoading && confirmed) { Adminx.Loader.show(); }
resolve(confirmed);
};
var onKeydown = function (event) {
if (event.key === 'Escape' && !submit.disabled) { finish(false); }
};
overlay.querySelectorAll('[data-reauth-cancel]').forEach(function (button) {
button.addEventListener('click', function () { if (!submit.disabled) { finish(false); } });
});
overlay.addEventListener('click', function (event) {
if (event.target === overlay && !submit.disabled) { finish(false); }
});
form.addEventListener('submit', function (event) {
event.preventDefault();
error.hidden = true;
submit.disabled = true;
var data = new FormData();
data.append('password', input.value);
fetch(config.reauth_url || (Adminx.base() + '/reauth'), {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': Adminx.csrf(),
'Accept': 'application/json'
},
credentials: 'same-origin',
body: data
}).then(function (response) {
return response.json().catch(function () { return {}; }).then(function (body) {
return { ok: response.ok, body: body };
});
}).then(function (result) {
if (result.ok && result.body.success) {
finish(true);
return;
}
error.textContent = result.body.message || Adminx.t('reauth_error', 'Не удалось подтвердить пароль');
error.hidden = false;
input.select();
submit.disabled = false;
}).catch(function () {
error.textContent = Adminx.t('server_unavailable', 'Сервер недоступен. Повторите попытку.');
error.hidden = false;
submit.disabled = false;
});
});
document.body.appendChild(overlay);
document.addEventListener('keydown', onKeydown);
window.setTimeout(function () { input.focus(); }, 20);
});
}
};
// ------------------------------------------------------------------ //
// Ajax: единый JSON-контракт success/error из App\Common\Controller
// ------------------------------------------------------------------ //
Adminx.Ajax = {
request: function (url, options) {
options = options || {};
var headers = options.headers || {};
headers['X-Requested-With'] = 'XMLHttpRequest';
headers['Accept'] = 'application/json';
if ((options.method || 'GET').toUpperCase() !== 'GET') {
headers['X-CSRF-Token'] = Adminx.csrf();
}
return fetch(url, {
method: options.method || 'GET',
headers: headers,
credentials: 'same-origin',
body: options.body || null
}).then(function (res) {
return res.json().catch(function () { return {}; }).then(function (data) {
return { ok: res.ok, status: res.status, data: data };
});
}).then(function (payload) {
var responseData = payload.data && payload.data.data ? payload.data.data : {};
if (payload.status !== 428 || !responseData.reauth_required || options._reauthRetried) {
return payload;
}
return Adminx.ReAuth.confirm(responseData).then(function (confirmed) {
if (!confirmed) { return payload; }
var retryOptions = {};
Object.keys(options).forEach(function (key) { retryOptions[key] = options[key]; });
retryOptions._reauthRetried = true;
return Adminx.Ajax.request(url, retryOptions);
});
});
},
post: function (url, formData) {
return this.request(url, { method: 'POST', body: formData });
},
/** Обработать стандартный ответ: тост + редирект. */
handle: function (payload) {
var d = payload.data || {};
if (d.message) {
Adminx.Toast.show(Adminx.tr(d.message), d.success ? 'success' : 'error');
}
if (d.redirect) {
window.location.href = d.redirect;
}
return d;
}
};
// ------------------------------------------------------------------ //
Adminx.Sidebar = {
KEY: 'adminx-sidebar',
GROUPS_KEY: 'adminx-sidebar-groups',
init: function () {
var self = this;
var toggle = document.getElementById('sidebarToggle');
var backdrop = document.getElementById('sidebarBackdrop');
if (toggle) {
toggle.addEventListener('click', function () {
if (window.matchMedia('(max-width: 900px)').matches) {
document.body.classList.toggle('sidebar-open');
} else {
var collapsed = document.body.classList.toggle('sidebar-collapsed');
//-- Запоминаем состояние, чтобы оно пережило переход по разделам.
try { localStorage.setItem(self.KEY, collapsed ? 'collapsed' : 'expanded'); } catch (e) {}
}
});
}
if (backdrop) {
backdrop.addEventListener('click', function () {
document.body.classList.remove('sidebar-open');
});
}
document.addEventListener('click', function (event) {
var trigger = event.target.closest('[data-nav-sub]');
if (!trigger) { return; }
var submenu = document.getElementById(trigger.getAttribute('data-nav-sub'));
if (!submenu) { return; }
var open = trigger.getAttribute('aria-expanded') !== 'true';
trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
submenu.classList.toggle('open', open);
});
this.initGroups();
this.initFlyout();
},
//-- Группы меню: пользователь оставляет открытыми только нужные разделы,
//-- а группа текущей страницы раскрывается независимо от сохранённого состояния.
initGroups: function () {
var sidebar = document.getElementById('sidebar');
if (!sidebar) { return; }
var groups = sidebar.querySelectorAll('[data-nav-group]');
var saved = {};
try {
var parsed = JSON.parse(localStorage.getItem(this.GROUPS_KEY) || '{}');
saved = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
} catch (e) {}
var hasActive = !!sidebar.querySelector('.nav-item.active');
groups.forEach(function (group, index) {
var trigger = group.querySelector(':scope > [data-nav-group-toggle]');
if (!trigger) { return; }
var key = group.getAttribute('data-nav-group-key') || '';
var active = !!group.querySelector('.nav-item.active');
var current = active || (!hasActive && index === 0);
var open = current || (Object.prototype.hasOwnProperty.call(saved, key) ? saved[key] === true : false);
group.classList.toggle('is-collapsed', !open);
group.classList.toggle('has-active', active);
trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
});
sidebar.classList.add('nav-groups-ready');
window.requestAnimationFrame(function () {
sidebar.classList.add('nav-groups-animated');
});
sidebar.addEventListener('click', function (event) {
var trigger = event.target.closest('[data-nav-group-toggle]');
if (!trigger || !sidebar.contains(trigger)) { return; }
var group = trigger.closest('[data-nav-group]');
var key = group ? group.getAttribute('data-nav-group-key') || '' : '';
if (!group || key === '') { return; }
var open = group.classList.contains('is-collapsed');
group.classList.toggle('is-collapsed', !open);
trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
saved[key] = open;
try { localStorage.setItem(Adminx.Sidebar.GROUPS_KEY, JSON.stringify(saved)); } catch (e) {}
});
},
//-- Свёрнутый сайдбар: подменю показывается всплывающим окном при наведении,
//-- чтобы вложенные пункты (Модули, Настройки…) оставались доступны.
initFlyout: function () {
var sidebar = document.getElementById('sidebar');
if (!sidebar) { return; }
var openSub = null, timer = null;
var collapsed = function () {
return document.body.classList.contains('sidebar-collapsed')
&& !window.matchMedia('(max-width: 900px)').matches;
};
var close = function () {
if (!openSub) { return; }
openSub.classList.remove('flyout-open');
openSub.style.top = ''; openSub.style.left = '';
openSub = null;
};
var cancel = function () { if (timer) { window.clearTimeout(timer); timer = null; } };
var schedule = function () { cancel(); timer = window.setTimeout(close, 200); };
var open = function (trigger, sub) {
cancel();
if (openSub && openSub !== sub) { close(); }
var r = trigger.getBoundingClientRect();
sub.style.left = (r.right + 6) + 'px';
sub.style.top = r.top + 'px';
sub.classList.add('flyout-open');
openSub = sub;
var sr = sub.getBoundingClientRect();
if (sr.bottom > window.innerHeight - 10) {
sub.style.top = Math.max(10, window.innerHeight - 10 - sr.height) + 'px';
}
};
sidebar.querySelectorAll('[data-nav-sub]').forEach(function (trigger) {
var sub = document.getElementById(trigger.getAttribute('data-nav-sub'));
if (!sub) { return; }
trigger.addEventListener('mouseenter', function () { if (collapsed()) { open(trigger, sub); } });
trigger.addEventListener('mouseleave', function () { if (collapsed()) { schedule(); } });
sub.addEventListener('mouseenter', function () { if (collapsed()) { cancel(); } });
sub.addEventListener('mouseleave', function () { if (collapsed()) { schedule(); } });
sub.addEventListener('click', function (e) { if (e.target.closest('a')) { close(); } });
});
window.addEventListener('resize', close);
window.addEventListener('scroll', close, true);
}
};
// ------------------------------------------------------------------ //
Adminx.Theme = {
KEY: 'adminx-theme',
init: function () {
var saved = null;
try { saved = localStorage.getItem(this.KEY); } catch (e) {}
if (saved) { document.documentElement.setAttribute('data-theme', saved); }
var btn = document.getElementById('themeToggle');
var self = this;
if (btn) {
this.sync(btn);
btn.addEventListener('click', function () {
self.toggle();
self.sync(btn);
});
}
},
sync: function (btn) {
var dark = document.documentElement.getAttribute('data-theme') === 'dark';
var icon = btn.querySelector('[data-theme-icon]');
var label = btn.querySelector('[data-theme-label]');
if (icon) {
icon.className = dark ? 'ti ti-sun' : 'ti ti-moon';
}
if (label) {
label.textContent = dark
? btn.getAttribute('data-theme-light-label')
: btn.getAttribute('data-theme-dark-label');
}
},
toggle: function () {
var cur = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', cur);
try { localStorage.setItem(this.KEY, cur); } catch (e) {}
}
};
// ------------------------------------------------------------------ //
Adminx.Dropdown = {
init: function () {
document.addEventListener('click', function (e) {
var trigger = e.target.closest('[data-dropdown]');
var openMenus = document.querySelectorAll('.dropdown.open');
if (trigger) {
var dd = trigger.closest('.dropdown');
var wasOpen = dd.classList.contains('open');
openMenus.forEach(function (m) { m.classList.remove('open'); });
if (!wasOpen) { dd.classList.add('open'); }
e.stopPropagation();
return;
}
openMenus.forEach(function (m) {
if (!m.contains(e.target)) { m.classList.remove('open'); }
});
});
}
};
// ------------------------------------------------------------------ //
// Меню пользователя в подвале сайдбара. Сайдбар имеет overflow:hidden,
// поэтому всплывающее меню позиционируем fixed вручную (над триггером),
// чтобы оно не обрезалось и работало в свёрнутом состоянии.
// ------------------------------------------------------------------ //
Adminx.SidebarUser = {
init: function () {
var dd = document.getElementById('sidebarUserMenu');
if (!dd) { return; }
var trigger = dd.querySelector('[data-dropdown]');
var menu = dd.querySelector('.dropdown-menu');
if (!trigger || !menu) { return; }
var place = function () {
if (!dd.classList.contains('open')) { return; }
var r = trigger.getBoundingClientRect();
menu.style.position = 'fixed';
menu.style.left = Math.round(r.left) + 'px';
menu.style.right = 'auto';
menu.style.top = 'auto';
menu.style.bottom = Math.round(window.innerHeight - r.top + 6) + 'px';
menu.style.minWidth = Math.max(220, Math.round(r.width)) + 'px';
};
// Dropdown.init переключает .open на том же клике; позиционируем после.
trigger.addEventListener('click', function () { setTimeout(place, 0); });
window.addEventListener('resize', place, { passive: true });
}
};
// Generic tabs for new shared surfaces. Existing module tabs can migrate to
// data-tab-target/data-tab-panel without adding another event listener.
Adminx.Tabs = {
activate: function (root, name) {
if (!root) { return; }
root.querySelectorAll('[data-tab-target]').forEach(function (tab) {
var active = tab.getAttribute('data-tab-target') === name;
tab.setAttribute('aria-selected', active ? 'true' : 'false');
tab.classList.toggle('active', active);
});
root.querySelectorAll('[data-tab-panel]').forEach(function (panel) {
panel.hidden = panel.getAttribute('data-tab-panel') !== name;
});
if (Adminx.CodeEditor) {
setTimeout(function () { Adminx.CodeEditor.refreshAll(); }, 0);
}
},
init: function () {
var self = this;
document.addEventListener('click', function (event) {
var tab = event.target.closest('[data-tab-target]');
if (!tab) { return; }
var root = tab.closest('[data-tabs]');
if (root) { self.activate(root, tab.getAttribute('data-tab-target')); }
});
}
};
// ------------------------------------------------------------------ //
// Drawer: боковая панель (открытие по [data-open-drawer="id"], закрытие
// по [data-close-drawer], клику по оверлею и Esc).
// ------------------------------------------------------------------ //
Adminx.Drawer = {
overlay: function () { return document.getElementById('drawerOverlay'); },
_seq: 0,
// Пересчёт стека открытых панелей: самая поздняя — сверху и активна,
// остальные получают .drawer-under (затеняются) под ней.
restack: function () {
var open = [].slice.call(document.querySelectorAll('.drawer.show'));
open.sort(function (a, b) {
return (Number(a.dataset.drawerSeq) || 0) - (Number(b.dataset.drawerSeq) || 0);
});
open.forEach(function (d, i) {
var isTop = i === open.length - 1;
d.classList.toggle('drawer-under', !isTop);
d.style.zIndex = String(195 + i * 2);
});
},
// AdminKit (fix-pack) открывает drawer/overlay классом .show (не .open).
open: function (id) {
var el = document.getElementById(id);
if (!el) { return; }
el.dataset.drawerSeq = String(++this._seq);
el.removeAttribute('hidden');
el.classList.add('show');
var ov = this.overlay();
if (ov) { ov.classList.add('show'); }
document.body.classList.add('drawer-active');
document.documentElement.classList.add('drawer-active');
this.restack();
},
close: function (id) {
var drawer = id ? document.getElementById(id) : null;
if (!drawer && typeof id === 'object' && id) { drawer = id; }
if (!drawer) {
var openDrawers = document.querySelectorAll('.drawer.show');
drawer = openDrawers.length ? openDrawers[openDrawers.length - 1] : null;
}
if (drawer) {
drawer.classList.remove('show');
drawer.classList.remove('drawer-under');
drawer.style.zIndex = '';
window.setTimeout(function () {
if (!drawer.classList.contains('show')) {
drawer.setAttribute('hidden', 'hidden');
document.dispatchEvent(new CustomEvent('adminx:drawer:closed', { detail: { drawer: drawer } }));
}
}, 230);
}
var ov = this.overlay();
if (!document.querySelector('.drawer.show')) {
if (ov) { ov.classList.remove('show'); }
document.body.classList.remove('drawer-active');
document.documentElement.classList.remove('drawer-active');
}
this.restack();
},
init: function () {
var self = this;
document.addEventListener('click', function (e) {
var opener = e.target.closest('[data-open-drawer]');
if (opener) { self.open(opener.getAttribute('data-open-drawer')); return; }
var closer = e.target.closest('[data-close-drawer]');
if (closer) { self.close(closer.closest('.drawer')); return; }
if (e.target.id === 'drawerOverlay') { self.close(); }
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') { self.close(); }
});
}
};
// ------------------------------------------------------------------ //
Adminx.Toast = {
region: function () {
var r = document.getElementById('toastRegion');
if (!r) {
r = document.createElement('div');
r.id = 'toastRegion';
r.className = 'toast-region';
document.body.appendChild(r);
}
return r;
},
show: function (message, type) {
var el = document.createElement('div');
el.className = 'toast toast-' + (type || 'info');
el.textContent = Adminx.tr(message);
this.region().appendChild(el);
setTimeout(function () { el.classList.add('show'); }, 10);
setTimeout(function () {
el.classList.remove('show');
setTimeout(function () { el.remove(); }, 250);
}, 3200);
}
};
// ------------------------------------------------------------------ //
// Confirm: модальный диалог подтверждения (компонент AdminKit .modal),
// вместо браузерного window.confirm. Никаких нативных промтов.
//
// Adminx.Confirm.open({ kind, title, message, confirmLabel, confirmClass,
// cancelLabel, onConfirm });
// ------------------------------------------------------------------ //
Adminx.Confirm = {
ICONS: {
info: ['info', 'ti-info-circle'],
success: ['success', 'ti-circle-check'],
warning: ['warning', 'ti-alert-triangle'],
error: ['error', 'ti-alert-circle']
},
open: function (cfg) {
cfg = cfg || {};
var kind = this.ICONS[cfg.kind] ? cfg.kind : 'warning';
var ic = this.ICONS[kind];
var overlay = document.createElement('div');
overlay.className = 'overlay';
var modal = document.createElement('div');
modal.className = 'modal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
modal.innerHTML =
'<div class="modal-header">' +
'<span class="dialog-icon ' + ic[0] + '"><i class="ti ' + ic[1] + '"></i></span>' +
'<div style="flex:1">' +
'<h3>' + esc(Adminx.tr(cfg.title || Adminx.t('confirm_title', 'Подтвердите действие'))) + '</h3>' +
(cfg.message ? '<p class="text-secondary" style="margin-top:4px">' + esc(Adminx.tr(cfg.message)) + '</p>' : '') +
'</div>' +
'<button class="modal-close" type="button" data-cancel aria-label="' + esc(Adminx.t('btn_close', 'Закрыть')) + '"><i class="ti ti-x"></i></button>' +
'</div>' +
'<div class="modal-footer">' +
'<button class="btn btn-ghost" type="button" data-cancel>' + esc(Adminx.tr(cfg.cancelLabel || Adminx.t('btn_cancel', 'Отмена'))) + '</button>' +
'<button class="btn ' + (cfg.confirmClass || 'btn-primary') + '" type="button" data-ok style="margin-left:auto">' + esc(Adminx.tr(cfg.confirmLabel || Adminx.t('confirm_action', 'Подтвердить'))) + '</button>' +
'</div>';
overlay.appendChild(modal);
document.body.appendChild(overlay);
// показываем (AdminKit fix-pack: overlay открывается классом .show)
requestAnimationFrame(function () { overlay.classList.add('show'); });
var self = this;
var close = function () {
overlay.classList.remove('show');
setTimeout(function () { overlay.remove(); document.removeEventListener('keydown', onKey); }, 180);
};
var onKey = function (e) { if (e.key === 'Escape') { close(); } };
overlay.addEventListener('click', function (e) {
if (e.target === overlay || e.target.closest('[data-cancel]')) { close(); return; }
if (e.target.closest('[data-ok]')) {
close();
if (typeof cfg.onConfirm === 'function') { cfg.onConfirm(); }
}
});
document.addEventListener('keydown', onKey);
modal.querySelector('[data-ok]').focus();
}
};
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
});
}
// ------------------------------------------------------------------ //
// Logout: подтверждение + POST через ajax-контракт (ТЗ §8.5)
// ------------------------------------------------------------------ //
Adminx.initLogout = function () {
document.addEventListener('click', function (e) {
var btn = e.target.closest('[data-confirm-logout]');
if (!btn) { return; }
e.preventDefault();
var form = document.getElementById('logoutForm');
if (!form) { return; }
Adminx.Confirm.open({
kind: 'info',
title: Adminx.t('logout_title', 'Выйти из админпанели?'),
message: Adminx.t('logout_message', 'Текущая сессия будет завершена.'),
confirmLabel: Adminx.t('btn_logout', 'Выйти'),
confirmClass: 'btn-primary',
onConfirm: function () {
Adminx.Loader.show();
var fd = new FormData(form);
Adminx.Ajax.post(form.getAttribute('action'), fd).then(function (payload) {
Adminx.Loader.hide();
var d = Adminx.Ajax.handle(payload);
if (!d.redirect) { window.location.href = Adminx.base() + '/login'; }
}).catch(function () {
Adminx.Loader.hide();
form.submit();
});
}
});
});
};
// ------------------------------------------------------------------ //
// Тема CodeMirror по умолчанию — dracula. Проставляем data-theme всем
// код-редакторам ДО их инициализации (adminx.js грузится раньше
// editor-codemirror.js), явный data-theme не трогаем. CSS темы подключает
// константа CODEMIRROR_THEME. Работает и для динамики (adminx:content-ready).
// ------------------------------------------------------------------ //
Adminx.CODE_THEME = 'dracula';
Adminx.applyCodeTheme = function (root) {
(root || document).querySelectorAll('textarea[data-code-editor]:not([data-theme])').forEach(function (t) {
t.setAttribute('data-theme', Adminx.CODE_THEME);
});
};
// ------------------------------------------------------------------ //
// Копирование тега в буфер (эталон — раздел «Системные блоки»). Любой
// элемент с data-ax-copy="[tag:...]" копирует значение и показывает тост.
// ------------------------------------------------------------------ //
Adminx.copyTag = function (text) {
text = String(text || '');
if (!text) { return; }
var ok = function () { Adminx.Toast.show(Adminx.t('tag_copied', 'Тег скопирован'), 'success'); };
var fallback = function () {
var ta = document.createElement('textarea');
ta.value = text;
ta.setAttribute('readonly', 'readonly');
ta.style.position = 'fixed';
ta.style.left = '-9999px';
ta.style.top = '0';
document.body.appendChild(ta);
ta.select();
try {
if (document.execCommand('copy')) { ok(); } else { Adminx.Toast.show(text, 'info'); }
} catch (e) { Adminx.Toast.show(text, 'info'); }
ta.remove();
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(ok).catch(fallback);
return;
}
fallback();
};
Adminx.initCopyTag = function () {
document.addEventListener('click', function (e) {
var el = e.target.closest('[data-ax-copy]');
if (!el) { return; }
e.preventDefault();
Adminx.copyTag(el.getAttribute('data-ax-copy'));
});
};
Adminx.initSectionHelp = function () {
var block = document.querySelector('[data-section-help]');
if (!block) { return; }
var header = document.querySelector('.page-header');
var details = block.querySelector('details');
//-- Инфо-аккордеон всегда свёрнут по умолчанию; открывается только по клику.
if (details) { details.open = false; }
if (header && header.parentNode) { header.insertAdjacentElement('afterend', block); }
block.removeAttribute('hidden');
};
// ------------------------------------------------------------------ //
// Tooltip: плавающая подсказка на уровне <body>. CSS-тултип (::after)
// обрезался любыми блоками с overflow (таблицы, канбан-колонки и т.п.);
// портал во <body> с position:fixed выходит за пределы любых блоков.
// ------------------------------------------------------------------ //
Adminx.Tooltip = {
el: null,
target: null,
init: function () {
var self = this;
document.addEventListener('pointerover', function (e) {
var t = e.target.closest ? e.target.closest('[data-tooltip]') : null;
if (t) { self.show(t); }
});
document.addEventListener('pointerout', function (e) {
var t = e.target.closest ? e.target.closest('[data-tooltip]') : null;
if (t && t === self.target) { self.hide(); }
});
document.addEventListener('focusin', function (e) {
var t = e.target.closest ? e.target.closest('[data-tooltip]') : null;
if (t) { self.show(t); } else { self.hide(); }
});
document.addEventListener('click', function () { self.hide(); });
window.addEventListener('scroll', function () { self.hide(); }, true);
window.addEventListener('resize', function () { self.hide(); });
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { self.hide(); } });
},
ensure: function () {
if (this.el) { return this.el; }
var el = document.createElement('div');
el.className = 'ax-tooltip';
el.setAttribute('role', 'tooltip');
document.body.appendChild(el);
this.el = el;
return el;
},
show: function (target) {
var text = target.getAttribute('data-tooltip');
if (!text) { return; }
this.target = target;
var el = this.ensure();
el.textContent = text;
this.position(target, el);
el.classList.add('show');
},
position: function (target, el) {
var r = target.getBoundingClientRect();
el.style.maxWidth = Math.min(280, window.innerWidth - 16) + 'px';
var tw = el.offsetWidth;
var th = el.offsetHeight;
var gap = 7;
var placement = target.getAttribute('data-tooltip-position') || 'top';
if (placement === 'right') {
var rightTop = r.top + r.height / 2 - th / 2;
var rightLeft = r.right + gap;
if (rightLeft + tw > window.innerWidth - 8) { rightLeft = r.left - tw - gap; }
el.style.top = Math.round(Math.max(8, Math.min(rightTop, window.innerHeight - th - 8))) + 'px';
el.style.left = Math.round(Math.max(8, rightLeft)) + 'px';
return;
}
var top = r.top - th - gap;
if (top < 8) { top = r.bottom + gap; }
var left = r.left + r.width / 2 - tw / 2;
left = Math.max(8, Math.min(left, window.innerWidth - tw - 8));
el.style.top = Math.round(top) + 'px';
el.style.left = Math.round(left) + 'px';
},
hide: function () {
this.target = null;
if (this.el) { this.el.classList.remove('show'); }
}
};
// ------------------------------------------------------------------ //
// Help: контекстная справка раздела в правой AJAX-панели.
// Триггер — любой элемент с data-ax-help="<slug>" (топбар, кнопка в
// разделе, подсказка у компонента). Контент грузится из /help/panel.
// ------------------------------------------------------------------ //
Adminx.HelpPanel = {
cache: {},
fill: function (drawer, d) {
var title = drawer.querySelector('#helpDrawerTitle');
var body = drawer.querySelector('[data-help-body]');
var full = drawer.querySelector('[data-help-full]');
if (title) { title.textContent = d.title || Adminx.t('help_title', 'Справка'); }
if (body) { body.innerHTML = d.html || ''; Adminx.applyCodeTheme(body); body.scrollTop = 0; }
if (full && d.url) { full.setAttribute('href', d.url); }
},
open: function (slug) {
if (!slug) { return; }
var drawer = document.getElementById('helpDrawer');
if (!drawer) { return; }
Adminx.Drawer.open('helpDrawer');
if (this.cache[slug]) { this.fill(drawer, this.cache[slug]); return; }
var body = drawer.querySelector('[data-help-body]');
if (body) { body.innerHTML = '<div class="help-drawer-state"><i class="ti ti-loader-2"></i>' + Adminx.t('help_loading', 'Загрузка…') + '</div>'; }
Adminx.Ajax.request(Adminx.base() + '/help/panel?doc=' + encodeURIComponent(slug)).then(function (p) {
var d = p.data || {};
if (!p.ok || !d.success) {
if (body) { body.innerHTML = '<div class="help-drawer-state"><i class="ti ti-file-off"></i>' + Adminx.t('help_not_found', 'Справка для этого раздела не найдена.') + '</div>'; }
return;
}
Adminx.HelpPanel.cache[slug] = d;
Adminx.HelpPanel.fill(drawer, d);
}).catch(function () {
if (body) { body.innerHTML = '<div class="help-drawer-state"><i class="ti ti-alert-triangle"></i>' + Adminx.t('help_load_error', 'Не удалось загрузить справку.') + '</div>'; }
});
},
init: function () {
document.addEventListener('click', function (e) {
var el = e.target.closest ? e.target.closest('[data-ax-help]') : null;
if (!el) { return; }
e.preventDefault();
e.stopPropagation();
Adminx.HelpPanel.open(el.getAttribute('data-ax-help'));
});
}
};
// ------------------------------------------------------------------ //
Adminx.init = function () {
Adminx.applyCodeTheme(document);
Adminx.Sidebar.init();
Adminx.Theme.init();
Adminx.Dropdown.init();
Adminx.SidebarUser.init();
Adminx.Tabs.init();
Adminx.Drawer.init();
Adminx.Tooltip.init();
Adminx.initLogout();
Adminx.initCopyTag();
Adminx.initSectionHelp();
Adminx.HelpPanel.init();
};
document.addEventListener('adminx:content-ready', function (e) {
Adminx.applyCodeTheme(e.detail && e.detail.root ? e.detail.root : document);
});
window.Adminx = Adminx;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', Adminx.init);
} else {
Adminx.init();
}
})(window, document);
+170
View File
@@ -0,0 +1,170 @@
/**
* Shared CodeMirror adapter for /adminx.
*
* Usage:
* <textarea data-code-editor data-mode="smartymixed" data-height="520"></textarea>
*/
(function (window, document) {
'use strict';
var Adminx = window.Adminx || (window.Adminx = {});
function boolAttr(el, name) {
var value = el.getAttribute(name);
return value === '' || value === '1' || value === 'true';
}
function numberAttr(el, name, fallback) {
var value = parseInt(el.getAttribute(name), 10);
return isNaN(value) || value <= 0 ? fallback : value;
}
function normalizeMode(mode) {
if (!mode) { return 'htmlmixed'; }
if (mode === 'php') { return 'application/x-httpd-php'; }
if (mode === 'smarty') { return 'smartymixed'; }
if (mode === 'js') { return 'text/javascript'; }
if (mode === 'css') { return 'text/css'; }
if (mode === 'sql') { return 'text/x-sql'; }
return mode;
}
Adminx.CodeEditor = {
instances: [],
init: function (root) {
root = root || document;
if (!window.CodeMirror) {
return;
}
var nodes = root.querySelectorAll('textarea[data-code-editor]:not([data-code-editor-ready])');
Array.prototype.forEach.call(nodes, this.create.bind(this));
},
create: function (textarea) {
var height = numberAttr(textarea, 'data-height', 420);
var theme = textarea.getAttribute('data-theme') ||
(document.documentElement.getAttribute('data-theme') === 'dark' ? 'material-ocean' : 'default');
var editor = window.CodeMirror.fromTextArea(textarea, {
mode: normalizeMode(textarea.getAttribute('data-mode')),
theme: theme,
lineNumbers: !boolAttr(textarea, 'data-no-lines'),
lineWrapping: !boolAttr(textarea, 'data-no-wrap'),
matchBrackets: true,
autoCloseTags: true,
styleActiveLine: true,
indentUnit: numberAttr(textarea, 'data-indent', 4),
indentWithTabs: true,
readOnly: textarea.readOnly || textarea.disabled || boolAttr(textarea, 'data-readonly'),
extraKeys: {
'Ctrl-S': function (cm) { Adminx.CodeEditor.saveForm(cm); },
'Cmd-S': function (cm) { Adminx.CodeEditor.saveForm(cm); },
'F11': function (cm) { Adminx.CodeEditor.toggleFullscreen(cm); },
'Cmd-F11': function (cm) { Adminx.CodeEditor.toggleFullscreen(cm); },
'Esc': function (cm) { Adminx.CodeEditor.closeFullscreen(cm); }
}
});
editor.setSize(textarea.getAttribute('data-width') || '100%', height);
editor.on('change', function (cm) {
cm.save();
textarea.dispatchEvent(new Event('input', { bubbles: true }));
});
textarea.setAttribute('data-code-editor-ready', '1');
textarea._adminxCodeMirror = editor;
this.instances.push(editor);
setTimeout(function () { editor.refresh(); }, 0);
},
saveForm: function (cm) {
var form, stayButton;
cm.save();
form = cm.getTextArea().closest('form');
if (!form) { return; }
stayButton = form.querySelector('[data-code-save-stay]');
if (stayButton && !stayButton.disabled) {
stayButton.click();
return;
}
if (typeof form.requestSubmit === 'function') {
form.requestSubmit();
} else {
form.submit();
}
},
toggleFullscreen: function (cm) {
var wrapper = cm.getWrapperElement();
if (wrapper.classList.contains('CodeMirror-fullscreen')) {
this.closeFullscreen(cm);
return;
}
wrapper.classList.add('CodeMirror-fullscreen');
document.body.style.overflow = 'hidden';
cm.refresh();
},
closeFullscreen: function (cm) {
var wrapper = cm.getWrapperElement();
if (!wrapper.classList.contains('CodeMirror-fullscreen')) { return; }
wrapper.classList.remove('CodeMirror-fullscreen');
document.body.style.overflow = '';
cm.refresh();
},
refreshAll: function () {
this.instances.forEach(function (editor) { editor.refresh(); });
},
insert: function (textarea, value) {
if (!textarea || !value || textarea.disabled || textarea.readOnly) { return; }
if (textarea._adminxCodeMirror) {
textarea._adminxCodeMirror.replaceSelection(value);
textarea._adminxCodeMirror.focus();
textarea._adminxCodeMirror.save();
} else {
var start = typeof textarea.selectionStart === 'number' ? textarea.selectionStart : textarea.value.length;
var end = typeof textarea.selectionEnd === 'number' ? textarea.selectionEnd : start;
textarea.value = textarea.value.slice(0, start) + value + textarea.value.slice(end);
textarea.selectionStart = textarea.selectionEnd = start + value.length;
textarea.focus();
}
textarea.dispatchEvent(new Event('input', { bubbles: true }));
},
insertTag: function (button) {
var palette = button.closest('[data-code-editor-tags]');
var form = palette ? palette.closest('form') : null;
var name = palette ? palette.getAttribute('data-code-editor-tags') : '';
var textarea = form && name ? form.elements[name] : null;
this.insert(textarea, button.getAttribute('data-code-editor-tag') || '');
},
// Синхронизировать значения всех редакторов в их textarea перед отправкой формы.
syncAll: function (root) {
var scope = root && root.querySelectorAll ? root : document;
this.instances.forEach(function (editor) {
var ta = editor.getTextArea && editor.getTextArea();
if (ta && (scope === document || scope.contains(ta))) { editor.save(); }
});
}
};
document.addEventListener('adminx:content-ready', function (e) {
Adminx.CodeEditor.init(e.detail && e.detail.root ? e.detail.root : document);
});
document.addEventListener('click', function (e) {
var button = e.target.closest('[data-code-editor-tag]');
if (!button) { return; }
e.preventDefault();
Adminx.CodeEditor.insertTag(button);
});
window.addEventListener('resize', function () { Adminx.CodeEditor.refreshAll(); });
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { Adminx.CodeEditor.init(); });
} else {
Adminx.CodeEditor.init();
}
})(window, document);
File diff suppressed because one or more lines are too long
+168
View File
@@ -0,0 +1,168 @@
/**
* Единый медиа-пикер /adminx. Один компонент для всех мест, где выбирают файл
* из медиатеки (поля документов, значения по умолчанию рубрик, навигация,
* визуальный редактор). Дергает общий эндпоинт GET /media/picker.
*
* Использование:
* Adminx.MediaPicker.open({
* type: 'image' | 'file' | 'all', // что показывать (по умолчанию image)
* dir: '/uploads', // стартовая папка
* title, description, // тексты шапки (опц.)
* onPick: function (file) { ... }, // выбран файл из медиатеки
* onFolder: function (dir) { ... }, // выбрана открытая папка (опц.)
* onUrl: function (url) { ... } // опц.: показать «вставить по ссылке»
* });
*/
(function (window, document) {
'use strict';
var Adminx = window.Adminx || (window.Adminx = {});
function esc(value) {
return String(value == null ? '' : value).replace(/[&<>"]/g, function (c) {
return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
});
}
Adminx.MediaPicker = {
open: function (options) {
options = options || {};
var type = (options.type === 'file' || options.type === 'all') ? options.type : 'image';
var onPick = typeof options.onPick === 'function' ? options.onPick : function () {};
var onFolder = typeof options.onFolder === 'function' ? options.onFolder : null;
var onUrl = typeof options.onUrl === 'function' ? options.onUrl : null;
var base = (Adminx.base && Adminx.base()) || '';
var title = options.title || 'Выбрать файл';
var description = options.description || (type === 'image' ? 'Выберите изображение из медиатеки.' : 'Выберите файл из медиатеки.');
var placeholder = type === 'image' ? 'Поиск изображений' : 'Поиск файлов';
var overlay = document.createElement('div');
overlay.className = 'overlay media-picker-overlay';
overlay.innerHTML =
'<div class="modal picker-modal media-picker" role="dialog" aria-modal="true">'
+ '<div class="modal-header"><span class="dialog-icon info"><i class="ti ti-photo-plus"></i></span>'
+ '<div style="flex:1"><h3>' + esc(title) + '</h3><p class="text-secondary" style="margin-top:4px">' + esc(description) + '</p></div>'
+ '<button class="modal-close" type="button" data-mp-close aria-label="Закрыть"><i class="ti ti-x"></i></button></div>'
+ '<div class="modal-body">'
+ '<div class="media-picker-tools">'
+ '<div class="input-wrap media-picker-search"><i class="ti ti-search"></i><input class="input" type="search" placeholder="' + placeholder + '" data-mp-search></div>'
+ '<button class="btn btn-ghost btn-icon" type="button" data-mp-up data-tooltip="На уровень выше" aria-label="На уровень выше"><i class="ti ti-arrow-up"></i></button>'
+ (onUrl ? '<button class="btn btn-ghost btn-sm" type="button" data-mp-url-toggle><i class="ti ti-link"></i>По ссылке</button>' : '')
+ '</div>'
+ (onUrl ? '<div class="media-picker-url" data-mp-url hidden><input class="input" type="url" placeholder="https://…" data-mp-url-input><button class="btn btn-primary btn-sm" type="button" data-mp-url-apply><i class="ti ti-check"></i>Вставить</button></div>' : '')
+ '<div class="media-picker-crumbs" data-mp-crumbs></div>'
+ '<div class="media-picker-status" data-mp-status>Загрузка…</div>'
+ '<div class="media-picker-grid" data-mp-grid></div>'
+ '</div>'
+ '<div class="modal-footer"><div class="mf-left media-picker-count" data-mp-count></div>'
+ '<button class="btn btn-ghost btn-sm" type="button" data-mp-prev><i class="ti ti-chevron-left"></i>Назад</button>'
+ '<button class="btn btn-ghost btn-sm" type="button" data-mp-next>Дальше<i class="ti ti-chevron-right"></i></button>'
+ (onFolder ? '<button class="btn btn-primary" type="button" data-mp-folder-apply><i class="ti ti-folder-check"></i>' + esc(options.folderLabel || 'Выбрать эту папку') + '</button>' : '')
+ '<button class="btn btn-ghost" type="button" data-mp-close>Закрыть</button></div>'
+ '</div>';
document.body.appendChild(overlay);
window.requestAnimationFrame(function () { overlay.classList.add('show'); });
var state = { dir: options.dir || '/uploads', parent: '', page: 1, pages: 1, q: '', type: type };
var grid = overlay.querySelector('[data-mp-grid]');
var crumbs = overlay.querySelector('[data-mp-crumbs]');
var status = overlay.querySelector('[data-mp-status]');
var count = overlay.querySelector('[data-mp-count]');
var search = overlay.querySelector('[data-mp-search]');
var timer = null;
function close() {
overlay.classList.remove('show');
window.setTimeout(function () { overlay.remove(); document.removeEventListener('keydown', onKey); }, 160);
}
function onKey(e) { if (e.key === 'Escape') { close(); } }
function renderCrumbs(items) {
crumbs.innerHTML = '';
(items || []).forEach(function (item, index) {
if (index > 0) { crumbs.insertAdjacentHTML('beforeend', '<span class="media-picker-sep">/</span>'); }
crumbs.insertAdjacentHTML('beforeend', '<button class="media-picker-crumb" type="button" data-mp-dir="' + esc(item.path || '/uploads') + '">' + esc(item.title || item.path || 'uploads') + '</button>');
});
}
function render(data) {
var folders = data.folders || [];
var files = data.files || [];
renderCrumbs(data.breadcrumbs || []);
grid.innerHTML = '';
folders.forEach(function (folder) {
grid.insertAdjacentHTML('beforeend', '<button class="media-picker-item is-folder" type="button" data-mp-dir="' + esc(folder.path || '') + '"><span class="media-picker-thumb"><span class="media-picker-folder-icon"><i class="ti ti-folder"></i></span></span><span class="media-picker-meta"><b>' + esc(folder.name || '') + '</b><small><i class="ti ti-files"></i>' + esc(folder.count || 0) + ' объектов</small></span></button>');
});
files.forEach(function (file) {
var thumb = file.thumb_url || file.preview_url || file.url || '';
var isImage = /\.(jpe?g|png|gif|webp|bmp|svg)(\?.*)?$/i.test(thumb);
var preview = isImage ? '<img src="' + esc(thumb) + '" alt="">' : '<i class="ti ti-file"></i>';
var meta = (file.width && file.height ? file.width + 'x' + file.height : '') + (file.size_label ? ' · ' + file.size_label : '');
grid.insertAdjacentHTML('beforeend', '<button class="media-picker-item" type="button" data-mp-file="' + esc(JSON.stringify(file)) + '"><span class="media-picker-thumb">' + preview + (file.extension ? '<span class="media-picker-extension">' + esc(file.extension) + '</span>' : '') + '</span><span class="media-picker-meta"><b>' + esc(file.name || '') + '</b><small>' + esc(meta || file.extension || 'Файл') + '</small></span></button>');
});
status.hidden = folders.length + files.length > 0;
status.textContent = state.q ? 'Ничего не найдено' : (state.type === 'image' ? 'В этой папке нет изображений' : 'В этой папке нет файлов');
count.textContent = data.total ? 'Файлов: ' + data.total + ', страница ' + state.page + ' из ' + state.pages : '';
overlay.querySelector('[data-mp-prev]').disabled = state.page <= 1;
overlay.querySelector('[data-mp-next]').disabled = state.page >= state.pages;
overlay.querySelector('[data-mp-up]').disabled = !state.parent;
}
function load(dir, page) {
state.dir = dir || state.dir;
state.page = page || 1;
status.textContent = 'Загрузка…';
status.hidden = false;
grid.innerHTML = '';
var params = new URLSearchParams();
params.set('dir', state.dir);
params.set('page', state.page);
params.set('per_page', 36);
params.set('type', state.type);
if (state.q) { params.set('q', state.q); }
fetch(base + '/media/picker?' + params.toString(), { headers: { 'Accept': 'application/json' }, credentials: 'same-origin' })
.then(function (res) { return res.json(); })
.then(function (payload) {
var data = (payload && payload.data) || {};
state.dir = data.dir || state.dir;
state.parent = data.parent_dir || '';
state.page = data.page || 1;
state.pages = data.pages || 1;
render(data);
})
.catch(function () { status.textContent = 'Не удалось загрузить медиа'; status.hidden = false; });
}
overlay.addEventListener('click', function (e) {
if (e.target === overlay || e.target.closest('[data-mp-close]')) { close(); return; }
var dir = e.target.closest('[data-mp-dir]');
if (dir) { load(dir.getAttribute('data-mp-dir') || '/uploads', 1); return; }
var file = e.target.closest('[data-mp-file]');
if (file && !onFolder) { onPick(JSON.parse(file.getAttribute('data-mp-file') || '{}')); close(); return; }
if (e.target.closest('[data-mp-folder-apply]') && onFolder) { onFolder(state.dir); close(); return; }
if (e.target.closest('[data-mp-prev]') && state.page > 1) { load(state.dir, state.page - 1); }
if (e.target.closest('[data-mp-next]') && state.page < state.pages) { load(state.dir, state.page + 1); }
if (e.target.closest('[data-mp-up]') && state.parent) { load(state.parent, 1); }
if (onUrl) {
if (e.target.closest('[data-mp-url-toggle]')) {
var box = overlay.querySelector('[data-mp-url]');
box.hidden = !box.hidden;
if (!box.hidden) { box.querySelector('[data-mp-url-input]').focus(); }
}
if (e.target.closest('[data-mp-url-apply]')) {
var value = overlay.querySelector('[data-mp-url-input]').value.trim();
if (value) { onUrl(value); close(); }
}
}
});
search.addEventListener('input', function () {
window.clearTimeout(timer);
timer = window.setTimeout(function () { state.q = search.value.trim(); load(state.dir, 1); }, 220);
});
document.addEventListener('keydown', onKey);
load(state.dir, 1);
search.focus();
}
};
})(window, document);
+197
View File
@@ -0,0 +1,197 @@
(function (window, document) {
'use strict';
var Adminx = window.Adminx || (window.Adminx = {});
function parse(value, fallback) {
try { return JSON.parse(value); } catch (error) { return fallback; }
}
function formFor(root) {
var selector = root.getAttribute('data-filter-form');
return selector ? document.querySelector(selector) : null;
}
function fields(root) {
return parse(root.getAttribute('data-fields') || '[]', []);
}
function currentFilters(root) {
var form = formFor(root), result = {};
if (!form) { return result; }
fields(root).forEach(function (name) {
var field = form.elements[name];
result[name] = field ? String(field.value || '') : '';
});
return result;
}
function same(left, right, names) {
return names.every(function (name) {
return String(left[name] || '') === String(right[name] || '');
});
}
function sync(root) {
var select = root.querySelector('[data-saved-views-select]');
var remove = root.querySelector('[data-saved-views-delete]');
if (!select || !remove) { return; }
var current = currentFilters(root), names = fields(root), matched = '';
Array.prototype.some.call(select.options, function (option) {
if (!option.value) { return false; }
if (same(current, parse(option.getAttribute('data-filters') || '{}', {}), names)) {
matched = option.value;
return true;
}
return false;
});
select.value = matched;
remove.disabled = !matched;
}
function render(root, views, selected) {
var select = root.querySelector('[data-saved-views-select]');
if (!select) { return; }
select.innerHTML = '<option value="">Сохранённые представления</option>';
(views || []).forEach(function (view) {
var option = document.createElement('option');
option.value = view.id;
option.textContent = view.title;
option.setAttribute('data-filters', JSON.stringify(view.filters || {}));
select.appendChild(option);
});
select.value = selected || '';
sync(root);
}
function apply(root, option) {
var form = formFor(root);
if (!form || !option || !option.value) { return; }
var values = parse(option.getAttribute('data-filters') || '{}', {});
fields(root).forEach(function (name) {
if (form.elements[name]) { form.elements[name].value = values[name] || ''; }
});
if (form.elements.page) { form.elements.page.value = '1'; }
if (typeof form.requestSubmit === 'function') {
form.requestSubmit();
} else {
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
}
}
function request(root, url, data, successMessage) {
Adminx.Loader.show();
return Adminx.Ajax.post(url, data).then(function (payload) {
var response = payload.data || {};
if (!payload.ok || response.success === false) {
throw new Error(response.message || 'Не удалось сохранить представление');
}
Adminx.Toast.show(response.message || successMessage, 'success');
return response.data || {};
}).catch(function (error) {
Adminx.Toast.show(error.message || 'Не удалось сохранить представление', 'error');
throw error;
}).then(function (result) {
Adminx.Loader.hide();
return result;
}, function (error) {
Adminx.Loader.hide();
throw error;
});
}
document.addEventListener('change', function (event) {
var select = event.target.closest('[data-saved-views-select]');
if (!select) { return; }
var root = select.closest('[data-saved-views]');
root.querySelector('[data-saved-views-delete]').disabled = !select.value;
apply(root, select.options[select.selectedIndex]);
});
document.addEventListener('click', function (event) {
var create = event.target.closest('[data-saved-views-create]');
if (create) {
var createRoot = create.closest('[data-saved-views]');
var editor = createRoot.querySelector('[data-saved-views-editor]');
editor.hidden = false;
var title = editor.querySelector('[data-saved-views-title]');
title.value = '';
title.focus();
return;
}
var cancel = event.target.closest('[data-saved-views-cancel]');
if (cancel) {
cancel.closest('[data-saved-views-editor]').hidden = true;
return;
}
var submit = event.target.closest('[data-saved-views-submit]');
if (submit) {
var submitRoot = submit.closest('[data-saved-views]');
var input = submitRoot.querySelector('[data-saved-views-title]');
var name = input.value.trim();
if (!name) {
input.focus();
Adminx.Toast.show('Укажите название представления', 'warning');
return;
}
submit.disabled = true;
var data = new FormData();
data.set('_csrf', submitRoot.getAttribute('data-csrf') || '');
data.set('title', name);
data.set('filters', JSON.stringify(currentFilters(submitRoot)));
request(submitRoot, submitRoot.getAttribute('data-save-url'), data, 'Представление сохранено').then(function (result) {
render(submitRoot, result.views || [], result.id || '');
submitRoot.querySelector('[data-saved-views-editor]').hidden = true;
}).then(function () { submit.disabled = false; }, function () { submit.disabled = false; });
return;
}
var remove = event.target.closest('[data-saved-views-delete]');
if (remove && !remove.disabled) {
var removeRoot = remove.closest('[data-saved-views]');
var select = removeRoot.querySelector('[data-saved-views-select]');
var option = select.options[select.selectedIndex];
if (!option || !option.value) { return; }
Adminx.Confirm.open({
kind: 'danger',
title: 'Удалить представление?',
message: 'Набор фильтров «' + option.textContent + '» будет удалён только у вашей учётной записи.',
confirmLabel: 'Удалить',
confirmClass: 'btn-danger',
onConfirm: function () {
var data = new FormData();
data.set('_csrf', removeRoot.getAttribute('data-csrf') || '');
var url = removeRoot.getAttribute('data-delete-url').replace('__ID__', encodeURIComponent(option.value));
return request(removeRoot, url, data, 'Представление удалено').then(function (result) {
render(removeRoot, result.views || [], '');
});
}
});
}
});
document.addEventListener('keydown', function (event) {
if (!event.target.matches('[data-saved-views-title]')) { return; }
if (event.key === 'Enter') {
event.preventDefault();
event.target.closest('[data-saved-views]').querySelector('[data-saved-views-submit]').click();
} else if (event.key === 'Escape') {
event.target.closest('[data-saved-views-editor]').hidden = true;
}
});
function syncAll(root) {
Array.prototype.forEach.call((root || document).querySelectorAll('[data-saved-views]'), sync);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { syncAll(document); });
} else {
syncAll(document);
}
document.addEventListener('adminx:content-ready', function (event) {
syncAll(event.detail && event.detail.root ? event.detail.root : document);
});
})(window, document);
+772
View File
@@ -0,0 +1,772 @@
import { Editor, mergeAttributes } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import Link from '@tiptap/extension-link';
import Image from '@tiptap/extension-image';
import { Table } from '@tiptap/extension-table';
import TableRow from '@tiptap/extension-table-row';
import TableCell from '@tiptap/extension-table-cell';
import TableHeader from '@tiptap/extension-table-header';
import TextAlign from '@tiptap/extension-text-align';
var EnhancedImage = Image.extend({
addAttributes: function () {
return Object.assign({}, this.parent ? this.parent() : {}, {
class: { default: null },
webp: { default: null }
});
},
parseHTML: function () {
return [
{
tag: 'picture',
getAttrs: function (element) {
var image = element.querySelector('img[src]');
var source = element.querySelector('source[type="image/webp"], source[srcset$=".webp"]');
if (!image) { return false; }
return {
src: image.getAttribute('src'),
alt: image.getAttribute('alt'),
title: image.getAttribute('title'),
class: image.getAttribute('class'),
width: image.getAttribute('width'),
height: image.getAttribute('height'),
webp: source ? source.getAttribute('srcset') : null
};
}
},
{ tag: 'img[src]:not([src^="data:"])' }
];
},
renderHTML: function (context) {
var attributes = Object.assign({}, context.HTMLAttributes || {});
var webp = attributes.webp;
delete attributes.webp;
var image = ['img', mergeAttributes(this.options.HTMLAttributes, attributes)];
if (!webp) { return image; }
return ['picture', {}, ['source', { srcset: webp, type: 'image/webp' }], image];
}
});
(function (window, document) {
'use strict';
var Adminx = window.Adminx || (window.Adminx = {});
function el(tag, attrs, html) {
var node = document.createElement(tag);
Object.keys(attrs || {}).forEach(function (key) {
if (key === 'class') {
node.className = attrs[key];
} else if (key === 'dataset') {
Object.keys(attrs[key]).forEach(function (name) { node.dataset[name] = attrs[key][name]; });
} else {
node.setAttribute(key, attrs[key]);
}
});
if (html != null) {
node.innerHTML = html;
}
return node;
}
function icon(name) {
return '<i class="ti ti-' + name + '"></i>';
}
function promptValue(title, value, done) {
Adminx.RichEditor.modalInput({ title: title, value: value || '' }, done);
}
Adminx.RichEditor = {
instances: [],
init: function (root) {
root = root || document;
var nodes = root.querySelectorAll('textarea[data-rich-editor]:not([data-rich-editor-ready])');
Array.prototype.forEach.call(nodes, this.create.bind(this));
},
extensions: function () {
return [
StarterKit.configure({ link: false }),
Link.configure({
openOnClick: false,
autolink: true,
protocols: ['http', 'https', 'mailto', 'tel'],
HTMLAttributes: { rel: 'noopener noreferrer nofollow' }
}),
EnhancedImage.configure({ allowBase64: false }),
Table.configure({ resizable: true }),
TableRow,
TableHeader,
TableCell,
TextAlign.configure({ types: ['heading', 'paragraph'] })
];
},
create: function (textarea) {
var preset = textarea.getAttribute('data-rich-preset') || 'full';
var root = el('div', { class: 'rich-editor rich-editor-' + preset });
var toolbar = el('div', { class: 'rich-editor-toolbar', role: 'toolbar' });
var surface = el('div', { class: 'rich-editor-surface' });
var editorEl = el('div', { class: 'rich-editor-content' });
var configuredHeight = parseInt(textarea.getAttribute('data-editor-height'), 10) || 0;
if (configuredHeight > 0) {
configuredHeight = Math.max(140, Math.min(900, configuredHeight));
root.classList.add('rich-editor-fixed-height');
root.style.setProperty('--rich-editor-height', configuredHeight + 'px');
}
textarea.style.display = 'none';
textarea.parentNode.insertBefore(root, textarea);
root.appendChild(toolbar);
root.appendChild(surface);
surface.appendChild(editorEl);
root.appendChild(textarea);
var editor = new Editor({
element: editorEl,
extensions: this.extensions(),
content: textarea.value || '',
editorProps: {
attributes: {
class: 'rich-editor-prose',
spellcheck: textarea.getAttribute('spellcheck') || 'true'
},
handleDoubleClickOn: function (view, pos, node, nodePos) {
if (!node || node.type.name !== 'image') { return false; }
editor.chain().setNodeSelection(nodePos).run();
Adminx.RichEditor.openImageEditor(editor, node.attrs, nodePos);
return true;
}
},
onUpdate: function () {
textarea.value = editor.getHTML();
textarea.setAttribute('data-rich-editor-dirty', '1');
textarea.dispatchEvent(new Event('input', { bubbles: true }));
},
onSelectionUpdate: function () {
Adminx.RichEditor.syncToolbar(root, editor);
},
onTransaction: function () {
Adminx.RichEditor.syncToolbar(root, editor);
}
});
this.buildToolbar(toolbar, editor);
textarea.setAttribute('data-rich-editor-ready', '1');
textarea._adminxTiptap = editor;
this.instances.push(editor);
this.bindForm(textarea, editor);
this.syncToolbar(root, editor);
},
button: function (name, tooltip, iconName, run) {
var btn = el('button', {
class: 'btn btn-ghost btn-icon btn-sm rich-editor-btn',
type: 'button',
'data-rich-command': name,
'data-tooltip': tooltip,
'aria-label': tooltip
}, icon(iconName));
btn.addEventListener('click', function () { run(); });
return btn;
},
buildToolbar: function (toolbar, editor) {
var group;
var self = this;
function addGroup() {
group = el('div', { class: 'rich-editor-group' });
toolbar.appendChild(group);
}
function add(name, tooltip, iconName, run) {
group.appendChild(self.button(name, tooltip, iconName, run));
}
function addBlockSelect() {
var select = el('select', {
class: 'select rich-editor-block-select',
'data-rich-block-select': '',
'aria-label': 'Формат блока',
'data-tooltip': 'Формат блока'
});
[
['paragraph', 'Абзац'],
['heading-1', 'Заголовок H1'],
['heading-2', 'Заголовок H2'],
['heading-3', 'Заголовок H3'],
['heading-4', 'Заголовок H4'],
['heading-5', 'Заголовок H5'],
['heading-6', 'Заголовок H6']
].forEach(function (item) {
var option = el('option', { value: item[0] });
option.textContent = item[1];
select.appendChild(option);
});
select.addEventListener('change', function () {
if (select.value === 'paragraph') {
editor.chain().focus().setParagraph().run();
return;
}
editor.chain().focus().setHeading({ level: parseInt(select.value.replace('heading-', ''), 10) }).run();
});
group.appendChild(select);
}
addGroup();
add('undo', 'Отменить', 'arrow-back-up', function () { editor.chain().focus().undo().run(); });
add('redo', 'Повторить', 'arrow-forward-up', function () { editor.chain().focus().redo().run(); });
addGroup();
add('bold', 'Жирный', 'bold', function () { editor.chain().focus().toggleBold().run(); });
add('italic', 'Курсив', 'italic', function () { editor.chain().focus().toggleItalic().run(); });
add('strike', 'Зачеркнуть', 'strikethrough', function () { editor.chain().focus().toggleStrike().run(); });
add('code', 'Блок кода', 'code', function () { editor.chain().focus().toggleCodeBlock().run(); });
addGroup();
addBlockSelect();
add('bulletList', 'Маркированный список', 'list', function () { editor.chain().focus().toggleBulletList().run(); });
add('orderedList', 'Нумерованный список', 'list-numbers', function () { editor.chain().focus().toggleOrderedList().run(); });
add('blockquote', 'Цитата', 'blockquote', function () { editor.chain().focus().toggleBlockquote().run(); });
addGroup();
add('alignLeft', 'По левому краю', 'align-left', function () { editor.chain().focus().setTextAlign('left').run(); });
add('alignCenter', 'По центру', 'align-center', function () { editor.chain().focus().setTextAlign('center').run(); });
add('alignRight', 'По правому краю', 'align-right', function () { editor.chain().focus().setTextAlign('right').run(); });
addGroup();
add('link', 'Ссылка', 'link', function () { self.openLink(editor); });
add('image', 'Изображение', 'photo-plus', function () {
self.openMediaPicker(editor);
});
add('table', 'Таблица', 'table-plus', function () {
editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
});
add('showBlocks', 'Показать блоки', 'box-model-2', function () {
var root = toolbar.closest('.rich-editor');
if (!root) { return; }
root.classList.toggle('rich-editor-show-blocks');
self.syncToolbar(root, editor);
});
addGroup();
if (window.CodeMirror) {
add('source', 'Исходник', 'code-dots', function () { self.openSource(editor); });
}
add('clear', 'Очистить формат', 'eraser', function () { editor.chain().focus().unsetAllMarks().clearNodes().run(); });
},
syncToolbar: function (root, editor) {
var states = {
bold: editor.isActive('bold'),
italic: editor.isActive('italic'),
strike: editor.isActive('strike'),
code: editor.isActive('codeBlock'),
bulletList: editor.isActive('bulletList'),
orderedList: editor.isActive('orderedList'),
blockquote: editor.isActive('blockquote'),
alignLeft: editor.isActive({ textAlign: 'left' }),
alignCenter: editor.isActive({ textAlign: 'center' }),
alignRight: editor.isActive({ textAlign: 'right' }),
link: editor.isActive('link'),
showBlocks: root.classList.contains('rich-editor-show-blocks'),
source: root.classList.contains('rich-editor-source-open')
};
Object.keys(states).forEach(function (name) {
var btn = root.querySelector('[data-rich-command="' + name + '"]');
if (btn) { btn.setAttribute('aria-pressed', states[name] ? 'true' : 'false'); }
});
var blockSelect = root.querySelector('[data-rich-block-select]');
if (blockSelect) {
var blockValue = 'paragraph';
for (var level = 1; level <= 6; level++) {
if (editor.isActive('heading', { level: level })) { blockValue = 'heading-' + level; break; }
}
blockSelect.value = blockValue;
}
},
bindForm: function (textarea, editor) {
var form = textarea.closest('form');
if (!form) { return; }
form.addEventListener('submit', function () {
var root = textarea.closest('.rich-editor');
if (root && root.classList.contains('rich-editor-source-open') && root._richSourceEditor) {
root._richSourceEditor.save();
textarea.value = root._richSourceEditor.getValue();
return;
}
if (textarea.getAttribute('data-rich-editor-dirty') === '1') {
textarea.value = editor.getHTML();
}
});
},
openSource: function (editor) {
var root = editor.view.dom.closest('.rich-editor');
if (!root || !window.CodeMirror) { return; }
var surface = root.querySelector('.rich-editor-surface');
var storage = root.querySelector('textarea[data-rich-editor]');
var source = root.querySelector('[data-rich-inline-source]');
var opening = !root.classList.contains('rich-editor-source-open');
if (!opening) {
var html = root._richSourceEditor ? root._richSourceEditor.getValue() : (storage ? storage.value : '');
if (html !== editor.getHTML()) { editor.commands.setContent(html || '<p></p>'); }
if (storage) { storage.value = editor.getHTML(); }
root.classList.remove('rich-editor-source-open');
this.setSourceControls(root, false);
this.syncToolbar(root, editor);
editor.commands.focus();
return;
}
if (!source) {
source = el('div', { class: 'rich-editor-inline-source', 'data-rich-inline-source': '' });
var sourceTextarea = el('textarea', { 'data-rich-source-textarea': '' });
source.appendChild(sourceTextarea);
surface.parentNode.insertBefore(source, surface.nextSibling);
root._richSourceEditor = window.CodeMirror.fromTextArea(sourceTextarea, {
mode: 'htmlmixed',
theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'material-ocean' : 'default',
lineNumbers: true,
lineWrapping: true,
matchBrackets: true,
autoCloseTags: true,
styleActiveLine: true,
indentUnit: 2
});
root._richSourceEditor.on('change', function (cm) {
if (!storage || root._richSourceSyncing) { return; }
storage.value = cm.getValue();
storage.setAttribute('data-rich-editor-dirty', '1');
storage.dispatchEvent(new Event('input', { bubbles: true }));
});
}
var height = Math.max(260, Math.round(surface.getBoundingClientRect().height || 0));
root._richSourceSyncing = true;
root._richSourceEditor.setValue(editor.getHTML());
root._richSourceSyncing = false;
root._richSourceEditor.setSize('100%', height);
root.classList.add('rich-editor-source-open');
this.setSourceControls(root, true);
this.syncToolbar(root, editor);
setTimeout(function () { root._richSourceEditor.refresh(); root._richSourceEditor.focus(); }, 0);
},
setSourceControls: function (root, sourceOpen) {
root.querySelectorAll('.rich-editor-toolbar [data-rich-command], .rich-editor-toolbar [data-rich-block-select]').forEach(function (control) {
if (control.getAttribute('data-rich-command') === 'source') { return; }
control.disabled = !!sourceOpen;
});
},
openLink: function (editor) {
var self = this;
var attributes = editor.getAttributes('link') || {};
var overlay = el('div', { class: 'overlay rich-editor-link-overlay' });
var modal = el('div', { class: 'modal rich-editor-link-modal', role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'richEditorLinkTitle' });
modal.innerHTML =
'<div class="modal-header">' +
'<span class="dialog-icon info"><i class="ti ti-link"></i></span>' +
'<div style="flex:1"><h3 id="richEditorLinkTitle">Ссылка</h3><p class="text-secondary" style="margin-top:4px">Укажите адрес вручную или выберите его из системы.</p></div>' +
'<button class="modal-close" type="button" data-link-cancel aria-label="Закрыть"><i class="ti ti-x"></i></button>' +
'</div>' +
'<div class="modal-body rich-editor-link-body">' +
'<label class="field"><span class="field-label">Адрес ссылки</span><input class="input mono" type="text" value="' + this.escape(attributes.href || '') + '" placeholder="/catalog/item или https://example.com" data-link-url></label>' +
'<div class="rich-editor-link-sources">' +
'<button class="btn btn-secondary" type="button" data-link-document><i class="ti ti-file-search"></i>Выбрать документ</button>' +
'<button class="btn btn-secondary" type="button" data-link-media><i class="ti ti-photo-search"></i>Выбрать из медиа</button>' +
'</div>' +
'<div class="rich-editor-link-selection text-secondary" data-link-selection>Можно использовать внутренний или внешний адрес.</div>' +
'<label class="field"><span class="field-label">CSS-класс</span><input class="input mono" type="text" value="' + this.escape(attributes.class || '') + '" placeholder="Например: button button-primary" data-link-class><span class="field-hint">Можно указать несколько классов через пробел.</span></label>' +
'</div>' +
'<div class="modal-footer"><div class="mf-left"><button class="btn btn-ghost" type="button" data-link-unset><i class="ti ti-unlink"></i>Убрать ссылку</button></div>' +
'<button class="btn btn-ghost" type="button" data-link-cancel>Отмена</button><button class="btn btn-primary" type="button" data-link-apply><i class="ti ti-check"></i>Применить</button></div>';
overlay.appendChild(modal);
document.body.appendChild(overlay);
requestAnimationFrame(function () { overlay.classList.add('show'); });
var urlInput = overlay.querySelector('[data-link-url]');
var classInput = overlay.querySelector('[data-link-class]');
var selection = overlay.querySelector('[data-link-selection]');
var close = function () {
overlay.classList.remove('show');
setTimeout(function () { overlay.remove(); document.removeEventListener('keydown', onKey); }, 180);
};
var apply = function () {
var href = String(urlInput.value || '').trim();
var cssClass = String(classInput.value || '').trim().replace(/\s+/g, ' ');
if (!href) {
editor.chain().focus().unsetLink().run();
close();
return;
}
editor.chain().focus().extendMarkRange('link').setLink({ href: href, class: cssClass || null }).run();
close();
};
var setPicked = function (url, label) {
urlInput.value = url || '';
selection.textContent = label || 'Адрес выбран';
urlInput.dispatchEvent(new Event('input', { bubbles: true }));
};
var onKey = function (event) {
if (event.key === 'Escape') {
if (document.querySelector('.rich-editor-document-picker-overlay.show, .media-picker-overlay.show')) { return; }
close();
}
if (event.key === 'Enter' && event.target !== classInput) { event.preventDefault(); apply(); }
};
overlay.addEventListener('click', function (event) {
if (event.target === overlay || event.target.closest('[data-link-cancel]')) { close(); return; }
if (event.target.closest('[data-link-apply]')) { apply(); return; }
if (event.target.closest('[data-link-unset]')) { editor.chain().focus().unsetLink().run(); close(); return; }
if (event.target.closest('[data-link-document]')) {
self.openDocumentLinkPicker(function (item) {
var alias = String(item.alias || '').trim();
var url = alias ? (/^(?:[a-z]+:|\/|#)/i.test(alias) ? alias : '/' + alias) : '/index.php?id=' + item.id;
setPicked(url, 'Документ #' + item.id + ' · ' + (item.title || 'Без названия'));
});
return;
}
if (event.target.closest('[data-link-media]') && Adminx.MediaPicker) {
Adminx.MediaPicker.open({
type: 'all',
title: 'Файл для ссылки',
description: 'Выберите изображение или документ из медиатеки.',
onPick: function (file) { if (file && file.url) { setPicked(file.url, 'Медиа · ' + (file.name || file.url)); } },
onUrl: function (url) { if (url) { setPicked(url, 'Внешний адрес медиа'); } }
});
}
});
document.addEventListener('keydown', onKey);
urlInput.focus();
urlInput.select();
},
openDocumentLinkPicker: function (done) {
var self = this;
var overlay = el('div', { class: 'overlay rich-editor-document-picker-overlay' });
var modal = el('div', { class: 'modal picker-modal rich-editor-document-picker', role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'richEditorDocumentPickerTitle' });
modal.innerHTML =
'<div class="modal-header"><span class="dialog-icon info"><i class="ti ti-file-search"></i></span><div style="flex:1"><h3 id="richEditorDocumentPickerTitle">Документ для ссылки</h3><p class="text-secondary" style="margin-top:4px">Поиск по ID, названию или alias.</p></div><button class="modal-close" type="button" data-document-link-close aria-label="Закрыть"><i class="ti ti-x"></i></button></div>' +
'<div class="modal-body"><div class="input-wrap rich-editor-document-search"><i class="ti ti-search"></i><input class="input" type="search" placeholder="ID, название или alias" data-document-link-search></div><div class="rich-editor-document-status" data-document-link-status>Загрузка...</div><div class="rich-editor-document-list" data-document-link-list></div></div>' +
'<div class="modal-footer"><div class="mf-left text-secondary" data-document-link-count></div><button class="btn btn-ghost" type="button" data-document-link-close>Закрыть</button></div>';
overlay.appendChild(modal);
document.body.appendChild(overlay);
requestAnimationFrame(function () { overlay.classList.add('show'); });
var search = overlay.querySelector('[data-document-link-search]');
var list = overlay.querySelector('[data-document-link-list]');
var status = overlay.querySelector('[data-document-link-status]');
var count = overlay.querySelector('[data-document-link-count]');
var items = [];
var timer = null;
var request = 0;
var close = function () {
overlay.classList.remove('show');
setTimeout(function () { overlay.remove(); document.removeEventListener('keydown', onKey); }, 160);
};
var onKey = function (event) { if (event.key === 'Escape') { event.stopImmediatePropagation(); close(); } };
var load = function () {
var current = ++request;
var params = new URLSearchParams();
params.set('q', search.value.trim());
params.set('limit', 40);
status.hidden = false;
status.textContent = 'Загрузка...';
fetch((window.ADMINX_BASE || '/adminx') + '/documents/picker?' + params.toString(), { headers: { 'Accept': 'application/json' }, credentials: 'same-origin' })
.then(function (response) { return response.json(); })
.then(function (payload) {
if (current !== request) { return; }
items = ((payload.data || {}).items) || [];
list.innerHTML = items.map(function (item, index) {
return '<button class="rich-editor-document-item" type="button" data-document-link-index="' + index + '"><span class="rich-editor-document-id">#' + self.escape(item.id) + '</span><span><b>' + self.escape(item.title || 'Без названия') + '</b><small>/' + self.escape(String(item.alias || '').replace(/^\/+/, '') || 'без alias') + '</small></span><em>' + self.escape(item.rubric_title || ('Рубрика #' + item.rubric_id)) + '</em></button>';
}).join('');
status.hidden = items.length > 0;
status.textContent = search.value.trim() ? 'Ничего не найдено' : 'Документы не найдены';
count.textContent = items.length ? 'Показано: ' + items.length : '';
})
.catch(function () { items = []; list.innerHTML = ''; status.hidden = false; status.textContent = 'Не удалось загрузить документы'; });
};
overlay.addEventListener('click', function (event) {
if (event.target === overlay || event.target.closest('[data-document-link-close]')) { close(); return; }
var row = event.target.closest('[data-document-link-index]');
if (!row) { return; }
var item = items[parseInt(row.getAttribute('data-document-link-index'), 10)];
if (item) { done(item); close(); }
});
search.addEventListener('input', function () { clearTimeout(timer); timer = setTimeout(load, 220); });
document.addEventListener('keydown', onKey);
load();
search.focus();
},
openMediaPicker: function (editor) {
this.openImageEditor(editor, {}, null);
},
openImageEditor: function (editor, current, nodePos) {
var self = this;
current = current || {};
var editing = typeof nodePos === 'number';
var overlay = el('div', { class: 'overlay rich-editor-image-overlay' });
var modal = el('div', { class: 'modal rich-editor-image-modal', role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'richEditorImageTitle' });
modal.innerHTML =
'<div class="modal-header">' +
'<span class="dialog-icon info"><i class="ti ti-photo-edit"></i></span>' +
'<div style="flex:1"><h3 id="richEditorImageTitle">' + (editing ? 'Свойства изображения' : 'Вставка изображения') + '</h3><p class="text-secondary" style="margin-top:4px">Основной файл, WebP, оформление и пропорциональный размер.</p></div>' +
'<button class="modal-close" type="button" data-image-cancel aria-label="Закрыть"><i class="ti ti-x"></i></button>' +
'</div>' +
'<div class="modal-body rich-editor-image-body">' +
'<div class="rich-editor-image-preview" data-image-preview><div class="rich-editor-image-empty" data-image-empty><i class="ti ti-photo"></i><span>Изображение не выбрано</span></div><img alt="" data-image-preview-img hidden><div class="rich-editor-image-preview-size" data-image-preview-size></div></div>' +
'<div class="rich-editor-image-settings">' +
'<div class="field"><span class="field-label">Основное изображение</span><div class="input-group"><input class="input mono" type="text" value="' + this.escape(current.src || '') + '" placeholder="/uploads/image.jpg" data-image-src><button class="input-addon" type="button" data-image-pick="src" data-tooltip="Выбрать из медиа" aria-label="Выбрать основное изображение"><i class="ti ti-photo-search"></i></button></div></div>' +
'<label class="switch rich-editor-image-picture-switch"><input type="checkbox" data-image-picture' + (current.webp ? ' checked' : '') + '><span></span><b>Использовать picture и WebP</b></label>' +
'<div class="field" data-image-webp-field' + (current.webp ? '' : ' hidden') + '><span class="field-label">Источник WebP</span><div class="input-group"><input class="input mono" type="text" value="' + this.escape(current.webp || '') + '" placeholder="/uploads/image.webp" data-image-webp><button class="input-addon" type="button" data-image-pick="webp" data-tooltip="Выбрать WebP" aria-label="Выбрать WebP"><i class="ti ti-photo-search"></i></button></div></div>' +
'<label class="field"><span class="field-label">Альтернативный текст</span><input class="input" type="text" value="' + this.escape(current.alt || '') + '" placeholder="Что изображено" data-image-alt></label>' +
'<label class="field"><span class="field-label">CSS-класс</span><input class="input mono" type="text" value="' + this.escape(current.class || '') + '" placeholder="Например: content-image is-rounded" data-image-class><span class="field-hint">Можно указать несколько классов через пробел.</span></label>' +
'<fieldset class="rich-editor-image-size"><legend>Размер</legend>' +
'<div class="rich-editor-image-dimensions"><label class="field"><span class="field-label">Ширина, px</span><input class="input" type="number" min="1" step="1" value="' + this.escape(current.width || '') + '" data-image-width></label><span class="rich-editor-image-lock" data-tooltip="Пропорции сохраняются"><i class="ti ti-lock"></i></span><label class="field"><span class="field-label">Высота, px</span><input class="input" type="number" min="1" step="1" value="' + this.escape(current.height || '') + '" data-image-height></label></div>' +
'<div class="rich-editor-image-scale"><input type="range" min="10" max="200" step="1" value="100" data-image-scale><output data-image-scale-value>100%</output><button class="btn btn-ghost btn-sm" type="button" data-image-original><i class="ti ti-aspect-ratio"></i>Исходный размер</button></div>' +
'</fieldset>' +
'</div>' +
'</div>' +
'<div class="modal-footer"><div class="mf-left">' + (editing ? '<button class="btn btn-danger-soft" type="button" data-image-remove><i class="ti ti-trash"></i>Удалить</button>' : '') + '</div><button class="btn btn-ghost" type="button" data-image-cancel>Отмена</button><button class="btn btn-primary" type="button" data-image-apply><i class="ti ti-check"></i>' + (editing ? 'Сохранить' : 'Вставить') + '</button></div>';
overlay.appendChild(modal);
document.body.appendChild(overlay);
requestAnimationFrame(function () { overlay.classList.add('show'); });
var srcInput = overlay.querySelector('[data-image-src]');
var webpInput = overlay.querySelector('[data-image-webp]');
var pictureInput = overlay.querySelector('[data-image-picture]');
var webpField = overlay.querySelector('[data-image-webp-field]');
var altInput = overlay.querySelector('[data-image-alt]');
var classInput = overlay.querySelector('[data-image-class]');
var widthInput = overlay.querySelector('[data-image-width]');
var heightInput = overlay.querySelector('[data-image-height]');
var scaleInput = overlay.querySelector('[data-image-scale]');
var scaleValue = overlay.querySelector('[data-image-scale-value]');
var preview = overlay.querySelector('[data-image-preview-img]');
var empty = overlay.querySelector('[data-image-empty]');
var previewSize = overlay.querySelector('[data-image-preview-size]');
var state = {
naturalWidth: parseInt(current.width, 10) || 0,
naturalHeight: parseInt(current.height, 10) || 0,
ratio: current.width && current.height ? parseInt(current.width, 10) / parseInt(current.height, 10) : 0,
loadingSrc: ''
};
var updateScale = function () {
var width = parseInt(widthInput.value, 10) || 0;
var percent = state.naturalWidth && width ? Math.round(width / state.naturalWidth * 100) : 100;
percent = Math.max(10, Math.min(200, percent));
scaleInput.value = percent;
scaleValue.textContent = percent + '%';
previewSize.textContent = width && heightInput.value ? width + ' x ' + heightInput.value + ' px' : '';
};
var setDimensions = function (width, height, natural) {
width = Math.max(1, Math.round(Number(width) || 0));
height = Math.max(1, Math.round(Number(height) || 0));
if (!width || !height) { return; }
if (natural) {
state.naturalWidth = width;
state.naturalHeight = height;
state.ratio = width / height;
} else if (!state.ratio) {
state.ratio = width / height;
}
widthInput.value = width;
heightInput.value = height;
updateScale();
};
var loadPreview = function (src, dimensions) {
src = String(src || '').trim();
state.loadingSrc = src;
if (!src) {
preview.hidden = true;
empty.hidden = false;
previewSize.textContent = '';
return;
}
var probe = new window.Image();
probe.onload = function () {
if (state.loadingSrc !== src) { return; }
preview.src = src;
preview.hidden = false;
empty.hidden = true;
var naturalWidth = dimensions && dimensions.width ? dimensions.width : probe.naturalWidth;
var naturalHeight = dimensions && dimensions.height ? dimensions.height : probe.naturalHeight;
if (!widthInput.value || !heightInput.value || src !== current.src) {
setDimensions(naturalWidth, naturalHeight, true);
} else {
state.naturalWidth = naturalWidth || parseInt(widthInput.value, 10);
state.naturalHeight = naturalHeight || parseInt(heightInput.value, 10);
state.ratio = state.naturalWidth / state.naturalHeight;
updateScale();
}
};
probe.onerror = function () {
if (state.loadingSrc !== src) { return; }
preview.hidden = true;
empty.hidden = false;
empty.querySelector('span').textContent = 'Не удалось загрузить изображение';
};
probe.src = src;
};
var choose = function (target) {
if (!Adminx.MediaPicker) { return; }
Adminx.MediaPicker.open({
type: 'image',
title: target === 'webp' ? 'Выбрать WebP' : 'Выбрать изображение',
description: target === 'webp' ? 'Выберите файл WebP для тега source.' : 'Выберите основное изображение из медиатеки.',
onPick: function (file) {
if (!file || !file.url) { return; }
if (target === 'webp') {
webpInput.value = file.url;
pictureInput.checked = true;
webpField.hidden = false;
return;
}
srcInput.value = file.url;
if (!altInput.value && file.name) { altInput.value = String(file.name).replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' '); }
widthInput.value = '';
heightInput.value = '';
loadPreview(file.url, file);
},
onUrl: function (url) {
if (!url) { return; }
if (target === 'webp') {
webpInput.value = url;
pictureInput.checked = true;
webpField.hidden = false;
} else {
srcInput.value = url;
widthInput.value = '';
heightInput.value = '';
loadPreview(url);
}
}
});
};
var close = function () {
overlay.classList.remove('show');
setTimeout(function () { overlay.remove(); document.removeEventListener('keydown', onKey); }, 180);
};
var apply = function () {
var src = String(srcInput.value || '').trim();
if (!src) { srcInput.focus(); return; }
var attributes = {
src: src,
webp: pictureInput.checked ? String(webpInput.value || '').trim() || null : null,
alt: String(altInput.value || '').trim() || null,
class: String(classInput.value || '').trim().replace(/\s+/g, ' ') || null,
width: parseInt(widthInput.value, 10) || null,
height: parseInt(heightInput.value, 10) || null
};
if (editing) {
editor.chain().focus().setNodeSelection(nodePos).updateAttributes('image', attributes).run();
} else {
editor.chain().focus().setImage(attributes).run();
}
close();
};
var onKey = function (event) {
if (event.key === 'Escape') {
if (document.querySelector('.media-picker-overlay.show')) { return; }
close();
}
};
pictureInput.addEventListener('change', function () { webpField.hidden = !pictureInput.checked; if (pictureInput.checked) { webpInput.focus(); } });
srcInput.addEventListener('change', function () { widthInput.value = ''; heightInput.value = ''; loadPreview(srcInput.value); });
widthInput.addEventListener('input', function () {
var width = parseInt(widthInput.value, 10) || 0;
if (width && state.ratio) { heightInput.value = Math.max(1, Math.round(width / state.ratio)); }
updateScale();
});
heightInput.addEventListener('input', function () {
var height = parseInt(heightInput.value, 10) || 0;
if (height && state.ratio) { widthInput.value = Math.max(1, Math.round(height * state.ratio)); }
updateScale();
});
scaleInput.addEventListener('input', function () {
var percent = parseInt(scaleInput.value, 10) || 100;
scaleValue.textContent = percent + '%';
if (state.naturalWidth && state.naturalHeight) {
widthInput.value = Math.max(1, Math.round(state.naturalWidth * percent / 100));
heightInput.value = Math.max(1, Math.round(state.naturalHeight * percent / 100));
previewSize.textContent = widthInput.value + ' x ' + heightInput.value + ' px';
}
});
overlay.addEventListener('click', function (event) {
if (event.target === overlay || event.target.closest('[data-image-cancel]')) { close(); return; }
var pick = event.target.closest('[data-image-pick]');
if (pick) { choose(pick.getAttribute('data-image-pick')); return; }
if (event.target.closest('[data-image-original]') && state.naturalWidth && state.naturalHeight) { setDimensions(state.naturalWidth, state.naturalHeight, false); return; }
if (event.target.closest('[data-image-apply]')) { apply(); return; }
if (event.target.closest('[data-image-remove]') && editing) { editor.chain().focus().setNodeSelection(nodePos).deleteSelection().run(); close(); }
});
document.addEventListener('keydown', onKey);
if (current.src) { loadPreview(current.src); } else { srcInput.focus(); }
},
modalInput: function (cfg, done) {
var overlay = el('div', { class: 'overlay' });
var modal = el('div', { class: 'modal rich-editor-prompt', role: 'dialog', 'aria-modal': 'true' });
modal.innerHTML =
'<div class="modal-header">' +
'<span class="dialog-icon info"><i class="ti ti-pencil"></i></span>' +
'<div style="flex:1"><h3>' + this.escape(cfg.title || 'Введите значение') + '</h3></div>' +
'<button class="modal-close" type="button" data-cancel aria-label="Закрыть"><i class="ti ti-x"></i></button>' +
'</div>' +
'<div class="modal-body"><input class="input" type="text" value="' + this.escape(cfg.value || '') + '" data-input></div>' +
'<div class="modal-footer"><button class="btn btn-ghost" type="button" data-cancel>Отмена</button>' +
'<button class="btn btn-primary" type="button" data-ok style="margin-left:auto">Применить</button></div>';
overlay.appendChild(modal);
document.body.appendChild(overlay);
requestAnimationFrame(function () { overlay.classList.add('show'); });
var input = overlay.querySelector('[data-input]');
var close = function () {
overlay.classList.remove('show');
setTimeout(function () { overlay.remove(); document.removeEventListener('keydown', onKey); }, 180);
};
var apply = function () {
var value = input.value || '';
close();
done(value);
};
var onKey = function (e) {
if (e.key === 'Escape') { close(); }
if (e.key === 'Enter') { apply(); }
};
overlay.addEventListener('click', function (e) {
if (e.target === overlay || e.target.closest('[data-cancel]')) { close(); }
if (e.target.closest('[data-ok]')) { apply(); }
});
document.addEventListener('keydown', onKey);
input.focus();
input.select();
},
escape: function (value) {
return String(value == null ? '' : value).replace(/[&<>"]/g, function (c) {
return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
});
}
};
document.addEventListener('adminx:content-ready', function (e) {
Adminx.RichEditor.init(e.detail && e.detail.root ? e.detail.root : document);
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { Adminx.RichEditor.init(); });
} else {
Adminx.RichEditor.init();
}
})(window, document);
+32
View File
@@ -0,0 +1,32 @@
.CodeMirror-dialog {
position: absolute;
left: 0; right: 0;
background: inherit;
z-index: 15;
padding: .1em .8em;
overflow: hidden;
color: inherit;
}
.CodeMirror-dialog-top {
border-bottom: 1px solid #eee;
top: 0;
}
.CodeMirror-dialog-bottom {
border-top: 1px solid #eee;
bottom: 0;
}
.CodeMirror-dialog input {
border: none;
outline: none;
background: transparent;
width: 20em;
color: inherit;
font-family: monospace;
}
.CodeMirror-dialog button {
font-size: 70%;
}
+161
View File
@@ -0,0 +1,161 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
// Open simple dialogs on top of an editor. Relies on dialog.css.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
function dialogDiv(cm, template, bottom) {
var wrap = cm.getWrapperElement();
var dialog;
dialog = wrap.appendChild(document.createElement("div"));
if (bottom)
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
else
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
if (typeof template == "string") {
dialog.innerHTML = template;
} else { // Assuming it's a detached DOM element.
dialog.appendChild(template);
}
CodeMirror.addClass(wrap, 'dialog-opened');
return dialog;
}
function closeNotification(cm, newVal) {
if (cm.state.currentNotificationClose)
cm.state.currentNotificationClose();
cm.state.currentNotificationClose = newVal;
}
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
if (!options) options = {};
closeNotification(this, null);
var dialog = dialogDiv(this, template, options.bottom);
var closed = false, me = this;
function close(newVal) {
if (typeof newVal == 'string') {
inp.value = newVal;
} else {
if (closed) return;
closed = true;
CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
dialog.parentNode.removeChild(dialog);
me.focus();
if (options.onClose) options.onClose(dialog);
}
}
var inp = dialog.getElementsByTagName("input")[0], button;
if (inp) {
inp.focus();
if (options.value) {
inp.value = options.value;
if (options.selectValueOnOpen !== false) {
inp.select();
}
}
if (options.onInput)
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
if (options.onKeyUp)
CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
CodeMirror.on(inp, "keydown", function(e) {
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
inp.blur();
CodeMirror.e_stop(e);
close();
}
if (e.keyCode == 13) callback(inp.value, e);
});
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
} else if (button = dialog.getElementsByTagName("button")[0]) {
CodeMirror.on(button, "click", function() {
close();
me.focus();
});
if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
button.focus();
}
return close;
});
CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
closeNotification(this, null);
var dialog = dialogDiv(this, template, options && options.bottom);
var buttons = dialog.getElementsByTagName("button");
var closed = false, me = this, blurring = 1;
function close() {
if (closed) return;
closed = true;
CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
dialog.parentNode.removeChild(dialog);
me.focus();
}
buttons[0].focus();
for (var i = 0; i < buttons.length; ++i) {
var b = buttons[i];
(function(callback) {
CodeMirror.on(b, "click", function(e) {
CodeMirror.e_preventDefault(e);
close();
if (callback) callback(me);
});
})(callbacks[i]);
CodeMirror.on(b, "blur", function() {
--blurring;
setTimeout(function() { if (blurring <= 0) close(); }, 200);
});
CodeMirror.on(b, "focus", function() { ++blurring; });
}
});
/*
* openNotification
* Opens a notification, that can be closed with an optional timer
* (default 5000ms timer) and always closes on click.
*
* If a notification is opened while another is opened, it will close the
* currently opened one and open the new one immediately.
*/
CodeMirror.defineExtension("openNotification", function(template, options) {
closeNotification(this, close);
var dialog = dialogDiv(this, template, options && options.bottom);
var closed = false, doneTimer;
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
function close() {
if (closed) return;
closed = true;
clearTimeout(doneTimer);
CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
dialog.parentNode.removeChild(dialog);
}
CodeMirror.on(dialog, 'click', function(e) {
CodeMirror.e_preventDefault(e);
close();
});
if (duration)
doneTimer = setTimeout(close, duration);
return close;
});
});
+184
View File
@@ -0,0 +1,184 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
/**
* Tag-closer extension for CodeMirror.
*
* This extension adds an "autoCloseTags" option that can be set to
* either true to get the default behavior, or an object to further
* configure its behavior.
*
* These are supported options:
*
* `whenClosing` (default true)
* Whether to autoclose when the '/' of a closing tag is typed.
* `whenOpening` (default true)
* Whether to autoclose the tag when the final '>' of an opening
* tag is typed.
* `dontCloseTags` (default is empty tags for HTML, none for XML)
* An array of tag names that should not be autoclosed.
* `indentTags` (default is block tags for HTML, none for XML)
* An array of tag names that should, when opened, cause a
* blank line to be added inside the tag, and the blank line and
* closing line to be indented.
* `emptyTags` (default is none)
* An array of XML tag names that should be autoclosed with '/>'.
*
* See demos/closetag.html for a usage example.
*/
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../fold/xml-fold"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
if (old != CodeMirror.Init && old)
cm.removeKeyMap("autoCloseTags");
if (!val) return;
var map = {name: "autoCloseTags"};
if (typeof val != "object" || val.whenClosing)
map["'/'"] = function(cm) { return autoCloseSlash(cm); };
if (typeof val != "object" || val.whenOpening)
map["'>'"] = function(cm) { return autoCloseGT(cm); };
cm.addKeyMap(map);
});
var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
"source", "track", "wbr"];
var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
"h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
function autoCloseGT(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
var opt = cm.getOption("autoCloseTags");
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
var tagInfo = inner.mode.xmlCurrentTag && inner.mode.xmlCurrentTag(state)
var tagName = tagInfo && tagInfo.name
if (!tagName) return CodeMirror.Pass
var html = inner.mode.configuration == "html";
var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
var lowerTagName = tagName.toLowerCase();
// Don't process the '>' at the end of an end-tag or self-closing tag
if (!tagName ||
tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
tok.type == "tag" && tagInfo.close ||
tok.string.indexOf("/") == (pos.ch - tok.start - 1) || // match something like <someTagName />
dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
closingTagExists(cm, inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state) || [], tagName, pos, true))
return CodeMirror.Pass;
var emptyTags = typeof opt == "object" && opt.emptyTags;
if (emptyTags && indexOf(emptyTags, tagName) > -1) {
replacements[i] = { text: "/>", newPos: CodeMirror.Pos(pos.line, pos.ch + 2) };
continue;
}
var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
replacements[i] = {indent: indent,
text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">",
newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};
}
var dontIndentOnAutoClose = (typeof opt == "object" && opt.dontIndentOnAutoClose);
for (var i = ranges.length - 1; i >= 0; i--) {
var info = replacements[i];
cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert");
var sel = cm.listSelections().slice(0);
sel[i] = {head: info.newPos, anchor: info.newPos};
cm.setSelections(sel);
if (!dontIndentOnAutoClose && info.indent) {
cm.indentLine(info.newPos.line, null, true);
cm.indentLine(info.newPos.line + 1, null, true);
}
}
}
function autoCloseCurrent(cm, typingSlash) {
var ranges = cm.listSelections(), replacements = [];
var head = typingSlash ? "/" : "</";
var opt = cm.getOption("autoCloseTags");
var dontIndentOnAutoClose = (typeof opt == "object" && opt.dontIndentOnSlash);
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" ||
tok.start != pos.ch - 1))
return CodeMirror.Pass;
// Kludge to get around the fact that we are not in XML mode
// when completing in JS/CSS snippet in htmlmixed mode. Does not
// work for other XML embedded languages (there is no general
// way to go from a mixed mode to its current XML state).
var replacement, mixed = inner.mode.name != "xml" && cm.getMode().name == "htmlmixed"
if (mixed && inner.mode.name == "javascript") {
replacement = head + "script";
} else if (mixed && inner.mode.name == "css") {
replacement = head + "style";
} else {
var context = inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state)
if (!context || (context.length && closingTagExists(cm, context, context[context.length - 1], pos)))
return CodeMirror.Pass;
replacement = head + context[context.length - 1]
}
if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">";
replacements[i] = replacement;
}
cm.replaceSelections(replacements);
ranges = cm.listSelections();
if (!dontIndentOnAutoClose) {
for (var i = 0; i < ranges.length; i++)
if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)
cm.indentLine(ranges[i].head.line);
}
}
function autoCloseSlash(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
return autoCloseCurrent(cm, true);
}
CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };
function indexOf(collection, elt) {
if (collection.indexOf) return collection.indexOf(elt);
for (var i = 0, e = collection.length; i < e; ++i)
if (collection[i] == elt) return i;
return -1;
}
// If xml-fold is loaded, we use its functionality to try and verify
// whether a given tag is actually unclosed.
function closingTagExists(cm, context, tagName, pos, newTag) {
if (!CodeMirror.scanForClosingTag) return false;
var end = Math.min(cm.lastLine() + 1, pos.line + 500);
var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);
if (!nextClose || nextClose.tag != tagName) return false;
// If the immediate wrapping context contains onCx instances of
// the same tag, a closing tag only exists if there are at least
// that many closing tags of that type following.
var onCx = newTag ? 1 : 0
for (var i = context.length - 1; i >= 0; i--) {
if (context[i] == tagName) ++onCx
else break
}
pos = nextClose.to;
for (var i = 1; i < onCx; i++) {
var next = CodeMirror.scanForClosingTag(cm, pos, null, end);
if (!next || next.tag != tagName) return false;
pos = next.to;
}
return true;
}
});
@@ -0,0 +1,150 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
(document.documentMode == null || document.documentMode < 8);
var Pos = CodeMirror.Pos;
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<", "<": ">>", ">": "<<"};
function bracketRegex(config) {
return config && config.bracketRegex || /[(){}[\]]/
}
function findMatchingBracket(cm, where, config) {
var line = cm.getLineHandle(where.line), pos = where.ch - 1;
var afterCursor = config && config.afterCursor
if (afterCursor == null)
afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className)
var re = bracketRegex(config)
// A cursor is defined as between two characters, but in in vim command mode
// (i.e. not insert mode), the cursor is visually represented as a
// highlighted box on top of the 2nd character. Otherwise, we allow matches
// from before or after the cursor.
var match = (!afterCursor && pos >= 0 && re.test(line.text.charAt(pos)) && matching[line.text.charAt(pos)]) ||
re.test(line.text.charAt(pos + 1)) && matching[line.text.charAt(++pos)];
if (!match) return null;
var dir = match.charAt(1) == ">" ? 1 : -1;
if (config && config.strict && (dir > 0) != (pos == where.ch)) return null;
var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
if (found == null) return null;
return {from: Pos(where.line, pos), to: found && found.pos,
match: found && found.ch == match.charAt(0), forward: dir > 0};
}
// bracketRegex is used to specify which type of bracket to scan
// should be a regexp, e.g. /[[\]]/
//
// Note: If "where" is on an open bracket, then this bracket is ignored.
//
// Returns false when no bracket was found, null when it reached
// maxScanLines and gave up
function scanForBracket(cm, where, dir, style, config) {
var maxScanLen = (config && config.maxScanLineLength) || 10000;
var maxScanLines = (config && config.maxScanLines) || 1000;
var stack = [];
var re = bracketRegex(config)
var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
: Math.max(cm.firstLine() - 1, where.line - maxScanLines);
for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
var line = cm.getLine(lineNo);
if (!line) continue;
var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
if (line.length > maxScanLen) continue;
if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
for (; pos != end; pos += dir) {
var ch = line.charAt(pos);
if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
var match = matching[ch];
if (match && (match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
else stack.pop();
}
}
}
return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;
}
function matchBrackets(cm, autoclear, config) {
// Disable brace matching in long lines, since it'll cause hugely slow updates
var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
var marks = [], ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config);
if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
}
}
if (marks.length) {
// Kludge to work around the IE bug from issue #1193, where text
// input stops going to the textare whever this fires.
if (ie_lt8 && cm.state.focused) cm.focus();
var clear = function() {
cm.operation(function() {
for (var i = 0; i < marks.length; i++) marks[i].clear();
});
};
if (autoclear) setTimeout(clear, 800);
else return clear;
}
}
function doMatchBrackets(cm) {
cm.operation(function() {
if (cm.state.matchBrackets.currentlyHighlighted) {
cm.state.matchBrackets.currentlyHighlighted();
cm.state.matchBrackets.currentlyHighlighted = null;
}
cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
});
}
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
cm.off("cursorActivity", doMatchBrackets);
if (cm.state.matchBrackets && cm.state.matchBrackets.currentlyHighlighted) {
cm.state.matchBrackets.currentlyHighlighted();
cm.state.matchBrackets.currentlyHighlighted = null;
}
}
if (val) {
cm.state.matchBrackets = typeof val == "object" ? val : {};
cm.on("cursorActivity", doMatchBrackets);
}
});
CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
CodeMirror.defineExtension("findMatchingBracket", function(pos, config, oldConfig){
// Backwards-compatibility kludge
if (oldConfig || typeof config == "boolean") {
if (!oldConfig) {
config = config ? {strict: true} : null
} else {
oldConfig.strict = config
config = oldConfig
}
}
return findMatchingBracket(this, pos, config)
});
CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
return scanForBracket(this, pos, dir, style, config);
});
});
+260
View File
@@ -0,0 +1,260 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
// Define search commands. Depends on dialog.js or another
// implementation of the openDialog method.
// Replace works a little oddly -- it will do the replace on the next
// Ctrl-G (or whatever is bound to findNext) press. You prevent a
// replace by making sure the match is no longer selected when hitting
// Ctrl-G.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function searchOverlay(query, caseInsensitive) {
if (typeof query == "string")
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
else if (!query.global)
query = new RegExp(query.source, query.ignoreCase ? "gi" : "g");
return {token: function(stream) {
query.lastIndex = stream.pos;
var match = query.exec(stream.string);
if (match && match.index == stream.pos) {
stream.pos += match[0].length || 1;
return "searching";
} else if (match) {
stream.pos = match.index;
} else {
stream.skipToEnd();
}
}};
}
function SearchState() {
this.posFrom = this.posTo = this.lastQuery = this.query = null;
this.overlay = null;
}
function getSearchState(cm) {
return cm.state.search || (cm.state.search = new SearchState());
}
function queryCaseInsensitive(query) {
return typeof query == "string" && query == query.toLowerCase();
}
function getSearchCursor(cm, query, pos) {
// Heuristic: if the query string is all lowercase, do a case insensitive search.
return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});
}
function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
cm.openDialog(text, onEnter, {
value: deflt,
selectValueOnOpen: true,
closeOnEnter: false,
onClose: function() { clearSearch(cm); },
onKeyDown: onKeyDown
});
}
function dialog(cm, text, shortText, deflt, f) {
if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
else f(prompt(shortText, deflt));
}
function confirmDialog(cm, text, shortText, fs) {
if (cm.openConfirm) cm.openConfirm(text, fs);
else if (confirm(shortText)) fs[0]();
}
function parseString(string) {
return string.replace(/\\([nrt\\])/g, function(match, ch) {
if (ch == "n") return "\n"
if (ch == "r") return "\r"
if (ch == "t") return "\t"
if (ch == "\\") return "\\"
return match
})
}
function parseQuery(query) {
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
if (isRE) {
try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
catch(e) {} // Not a regular expression after all, do a string search
} else {
query = parseString(query)
}
if (typeof query == "string" ? query == "" : query.test(""))
query = /x^/;
return query;
}
function startSearch(cm, state, query) {
state.queryText = query;
state.query = parseQuery(query);
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
cm.addOverlay(state.overlay);
if (cm.showMatchesOnScrollbar) {
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
}
}
function doSearch(cm, rev, persistent, immediate) {
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
var q = cm.getSelection() || state.lastQuery;
if (q instanceof RegExp && q.source == "x^") q = null
if (persistent && cm.openDialog) {
var hiding = null
var searchNext = function(query, event) {
CodeMirror.e_stop(event);
if (!query) return;
if (query != state.queryText) {
startSearch(cm, state, query);
state.posFrom = state.posTo = cm.getCursor();
}
if (hiding) hiding.style.opacity = 1
findNext(cm, event.shiftKey, function(_, to) {
var dialog
if (to.line < 3 && document.querySelector &&
(dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) &&
dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
(hiding = dialog).style.opacity = .4
})
};
persistentDialog(cm, getQueryDialog(cm), q, searchNext, function(event, query) {
var keyName = CodeMirror.keyName(event)
var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
if (cmd == "findNext" || cmd == "findPrev" ||
cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
CodeMirror.e_stop(event);
startSearch(cm, getSearchState(cm), query);
cm.execCommand(cmd);
} else if (cmd == "find" || cmd == "findPersistent") {
CodeMirror.e_stop(event);
searchNext(query, event);
}
});
if (immediate && q) {
startSearch(cm, state, q);
findNext(cm, rev);
}
} else {
dialog(cm, getQueryDialog(cm), "Search for:", q, function(query) {
if (query && !state.query) cm.operation(function() {
startSearch(cm, state, query);
state.posFrom = state.posTo = cm.getCursor();
findNext(cm, rev);
});
});
}
}
function findNext(cm, rev, callback) {cm.operation(function() {
var state = getSearchState(cm);
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
if (!cursor.find(rev)) {
cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
if (!cursor.find(rev)) return;
}
cm.setSelection(cursor.from(), cursor.to());
cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
state.posFrom = cursor.from(); state.posTo = cursor.to();
if (callback) callback(cursor.from(), cursor.to())
});}
function clearSearch(cm) {cm.operation(function() {
var state = getSearchState(cm);
state.lastQuery = state.query;
if (!state.query) return;
state.query = state.queryText = null;
cm.removeOverlay(state.overlay);
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
});}
function getQueryDialog(cm) {
return '<span class="CodeMirror-search-label">' + cm.phrase("Search:") + '</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">' + cm.phrase("(Use /re/ syntax for regexp search)") + '</span>';
}
function getReplaceQueryDialog(cm) {
return ' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">' + cm.phrase("(Use /re/ syntax for regexp search)") + '</span>';
}
function getReplacementQueryDialog(cm) {
return '<span class="CodeMirror-search-label">' + cm.phrase("With:") + '</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
}
function getDoReplaceConfirm(cm) {
return '<span class="CodeMirror-search-label">' + cm.phrase("Replace?") + '</span> <button>' + cm.phrase("Yes") + '</button> <button>' + cm.phrase("No") + '</button> <button>' + cm.phrase("All") + '</button> <button>' + cm.phrase("Stop") + '</button> ';
}
function replaceAll(cm, query, text) {
cm.operation(function() {
for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
if (typeof query != "string") {
var match = cm.getRange(cursor.from(), cursor.to()).match(query);
cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
} else cursor.replace(text);
}
});
}
function replace(cm, all) {
if (cm.getOption("readOnly")) return;
var query = cm.getSelection() || getSearchState(cm).lastQuery;
var dialogText = '<span class="CodeMirror-search-label">' + (all ? cm.phrase("Replace all:") : cm.phrase("Replace:")) + '</span>';
dialog(cm, dialogText + getReplaceQueryDialog(cm), dialogText, query, function(query) {
if (!query) return;
query = parseQuery(query);
dialog(cm, getReplacementQueryDialog(cm), cm.phrase("Replace with:"), "", function(text) {
text = parseString(text)
if (all) {
replaceAll(cm, query, text)
} else {
clearSearch(cm);
var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
var advance = function() {
var start = cursor.from(), match;
if (!(match = cursor.findNext())) {
cursor = getSearchCursor(cm, query);
if (!(match = cursor.findNext()) ||
(start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
}
cm.setSelection(cursor.from(), cursor.to());
cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
confirmDialog(cm, getDoReplaceConfirm(cm), cm.phrase("Replace?"),
[function() {doReplace(match);}, advance,
function() {replaceAll(cm, query, text)}]);
};
var doReplace = function(match) {
cursor.replace(typeof query == "string" ? text :
text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
advance();
};
advance();
}
});
});
}
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
CodeMirror.commands.findNext = doSearch;
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
CodeMirror.commands.clearSearch = clearSearch;
CodeMirror.commands.replace = replace;
CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
});
@@ -0,0 +1,296 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"))
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod)
else // Plain browser env
mod(CodeMirror)
})(function(CodeMirror) {
"use strict"
var Pos = CodeMirror.Pos
function regexpFlags(regexp) {
var flags = regexp.flags
return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
+ (regexp.global ? "g" : "")
+ (regexp.multiline ? "m" : "")
}
function ensureFlags(regexp, flags) {
var current = regexpFlags(regexp), target = current
for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)
target += flags.charAt(i)
return current == target ? regexp : new RegExp(regexp.source, target)
}
function maybeMultiline(regexp) {
return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
}
function searchRegexpForward(doc, regexp, start) {
regexp = ensureFlags(regexp, "g")
for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
regexp.lastIndex = ch
var string = doc.getLine(line), match = regexp.exec(string)
if (match)
return {from: Pos(line, match.index),
to: Pos(line, match.index + match[0].length),
match: match}
}
}
function searchRegexpForwardMultiline(doc, regexp, start) {
if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
regexp = ensureFlags(regexp, "gm")
var string, chunk = 1
for (var line = start.line, last = doc.lastLine(); line <= last;) {
// This grows the search buffer in exponentially-sized chunks
// between matches, so that nearby matches are fast and don't
// require concatenating the whole document (in case we're
// searching for something that has tons of matches), but at the
// same time, the amount of retries is limited.
for (var i = 0; i < chunk; i++) {
if (line > last) break
var curLine = doc.getLine(line++)
string = string == null ? curLine : string + "\n" + curLine
}
chunk = chunk * 2
regexp.lastIndex = start.ch
var match = regexp.exec(string)
if (match) {
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
return {from: Pos(startLine, startCh),
to: Pos(startLine + inside.length - 1,
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
match: match}
}
}
}
function lastMatchIn(string, regexp, endMargin) {
var match, from = 0
while (from <= string.length) {
regexp.lastIndex = from
var newMatch = regexp.exec(string)
if (!newMatch) break
var end = newMatch.index + newMatch[0].length
if (end > string.length - endMargin) break
if (!match || end > match.index + match[0].length)
match = newMatch
from = newMatch.index + 1
}
return match
}
function searchRegexpBackward(doc, regexp, start) {
regexp = ensureFlags(regexp, "g")
for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
var string = doc.getLine(line)
var match = lastMatchIn(string, regexp, ch < 0 ? 0 : string.length - ch)
if (match)
return {from: Pos(line, match.index),
to: Pos(line, match.index + match[0].length),
match: match}
}
}
function searchRegexpBackwardMultiline(doc, regexp, start) {
if (!maybeMultiline(regexp)) return searchRegexpBackward(doc, regexp, start)
regexp = ensureFlags(regexp, "gm")
var string, chunkSize = 1, endMargin = doc.getLine(start.line).length - start.ch
for (var line = start.line, first = doc.firstLine(); line >= first;) {
for (var i = 0; i < chunkSize && line >= first; i++) {
var curLine = doc.getLine(line--)
string = string == null ? curLine : curLine + "\n" + string
}
chunkSize *= 2
var match = lastMatchIn(string, regexp, endMargin)
if (match) {
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
var startLine = line + before.length, startCh = before[before.length - 1].length
return {from: Pos(startLine, startCh),
to: Pos(startLine + inside.length - 1,
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
match: match}
}
}
}
var doFold, noFold
if (String.prototype.normalize) {
doFold = function(str) { return str.normalize("NFD").toLowerCase() }
noFold = function(str) { return str.normalize("NFD") }
} else {
doFold = function(str) { return str.toLowerCase() }
noFold = function(str) { return str }
}
// Maps a position in a case-folded line back to a position in the original line
// (compensating for codepoints increasing in number during folding)
function adjustPos(orig, folded, pos, foldFunc) {
if (orig.length == folded.length) return pos
for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
if (min == max) return min
var mid = (min + max) >> 1
var len = foldFunc(orig.slice(0, mid)).length
if (len == pos) return mid
else if (len > pos) max = mid
else min = mid + 1
}
}
function searchStringForward(doc, query, start, caseFold) {
// Empty string would match anything and never progress, so we
// define it to match nothing instead.
if (!query.length) return null
var fold = caseFold ? doFold : noFold
var lines = fold(query).split(/\r|\n\r?/)
search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
var orig = doc.getLine(line).slice(ch), string = fold(orig)
if (lines.length == 1) {
var found = string.indexOf(lines[0])
if (found == -1) continue search
var start = adjustPos(orig, string, found, fold) + ch
return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),
to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}
} else {
var cutFrom = string.length - lines[0].length
if (string.slice(cutFrom) != lines[0]) continue search
for (var i = 1; i < lines.length - 1; i++)
if (fold(doc.getLine(line + i)) != lines[i]) continue search
var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
if (endString.slice(0, lastLine.length) != lastLine) continue search
return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}
}
}
}
function searchStringBackward(doc, query, start, caseFold) {
if (!query.length) return null
var fold = caseFold ? doFold : noFold
var lines = fold(query).split(/\r|\n\r?/)
search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
var orig = doc.getLine(line)
if (ch > -1) orig = orig.slice(0, ch)
var string = fold(orig)
if (lines.length == 1) {
var found = string.lastIndexOf(lines[0])
if (found == -1) continue search
return {from: Pos(line, adjustPos(orig, string, found, fold)),
to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}
} else {
var lastLine = lines[lines.length - 1]
if (string.slice(0, lastLine.length) != lastLine) continue search
for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
if (fold(doc.getLine(start + i)) != lines[i]) continue search
var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}
}
}
}
function SearchCursor(doc, query, pos, options) {
this.atOccurrence = false
this.doc = doc
pos = pos ? doc.clipPos(pos) : Pos(0, 0)
this.pos = {from: pos, to: pos}
var caseFold
if (typeof options == "object") {
caseFold = options.caseFold
} else { // Backwards compat for when caseFold was the 4th argument
caseFold = options
options = null
}
if (typeof query == "string") {
if (caseFold == null) caseFold = false
this.matches = function(reverse, pos) {
return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
}
} else {
query = ensureFlags(query, "gm")
if (!options || options.multiline !== false)
this.matches = function(reverse, pos) {
return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
}
else
this.matches = function(reverse, pos) {
return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
}
}
}
SearchCursor.prototype = {
findNext: function() {return this.find(false)},
findPrevious: function() {return this.find(true)},
find: function(reverse) {
var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
// Implements weird auto-growing behavior on null-matches for
// backwards-compatiblity with the vim code (unfortunately)
while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {
if (reverse) {
if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)
else if (result.from.line == this.doc.firstLine()) result = null
else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))
} else {
if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)
else if (result.to.line == this.doc.lastLine()) result = null
else result = this.matches(reverse, Pos(result.to.line + 1, 0))
}
}
if (result) {
this.pos = result
this.atOccurrence = true
return this.pos.match || true
} else {
var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
this.pos = {from: end, to: end}
return this.atOccurrence = false
}
},
from: function() {if (this.atOccurrence) return this.pos.from},
to: function() {if (this.atOccurrence) return this.pos.to},
replace: function(newText, origin) {
if (!this.atOccurrence) return
var lines = CodeMirror.splitLines(newText)
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
}
}
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this.doc, query, pos, caseFold)
})
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this, query, pos, caseFold)
})
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
var ranges = []
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
while (cur.findNext()) {
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
ranges.push({anchor: cur.from(), head: cur.to()})
}
if (ranges.length)
this.setSelections(ranges, 0)
})
});
@@ -0,0 +1,72 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var WRAP_CLASS = "CodeMirror-activeline";
var BACK_CLASS = "CodeMirror-activeline-background";
var GUTT_CLASS = "CodeMirror-activeline-gutter";
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
var prev = old == CodeMirror.Init ? false : old;
if (val == prev) return
if (prev) {
cm.off("beforeSelectionChange", selectionChange);
clearActiveLines(cm);
delete cm.state.activeLines;
}
if (val) {
cm.state.activeLines = [];
updateActiveLines(cm, cm.listSelections());
cm.on("beforeSelectionChange", selectionChange);
}
});
function clearActiveLines(cm) {
for (var i = 0; i < cm.state.activeLines.length; i++) {
cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS);
cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS);
cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS);
}
}
function sameArray(a, b) {
if (a.length != b.length) return false;
for (var i = 0; i < a.length; i++)
if (a[i] != b[i]) return false;
return true;
}
function updateActiveLines(cm, ranges) {
var active = [];
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
var option = cm.getOption("styleActiveLine");
if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty())
continue
var line = cm.getLineHandleVisualStart(range.head.line);
if (active[active.length - 1] != line) active.push(line);
}
if (sameArray(cm.state.activeLines, active)) return;
cm.operation(function() {
clearActiveLines(cm);
for (var i = 0; i < active.length; i++) {
cm.addLineClass(active[i], "wrap", WRAP_CLASS);
cm.addLineClass(active[i], "background", BACK_CLASS);
cm.addLineClass(active[i], "gutter", GUTT_CLASS);
}
cm.state.activeLines = active;
});
}
function selectionChange(cm, sel) {
updateActiveLines(cm, sel.ranges);
}
});
+6
View File
@@ -0,0 +1,6 @@
<?php
header('Location:../index.php?id=2');
exit;
?>
+349
View File
@@ -0,0 +1,349 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: black;
direction: ltr;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: 0;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -30px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre.CodeMirror-line,
.CodeMirror-wrap pre.CodeMirror-line-like {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, .4);
}
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }
File diff suppressed because it is too large Load Diff
+935
View File
@@ -0,0 +1,935 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function Context(indented, column, type, info, align, prev) {
this.indented = indented;
this.column = column;
this.type = type;
this.info = info;
this.align = align;
this.prev = prev;
}
function pushContext(state, col, type, info) {
var indent = state.indented;
if (state.context && state.context.type == "statement" && type != "statement")
indent = state.context.indented;
return state.context = new Context(indent, col, type, info, null, state.context);
}
function popContext(state) {
var t = state.context.type;
if (t == ")" || t == "]" || t == "}")
state.indented = state.context.indented;
return state.context = state.context.prev;
}
function typeBefore(stream, state, pos) {
if (state.prevToken == "variable" || state.prevToken == "type") return true;
if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, pos))) return true;
if (state.typeAtEndOfLine && stream.column() == stream.indentation()) return true;
}
function isTopScope(context) {
for (;;) {
if (!context || context.type == "top") return true;
if (context.type == "}" && context.prev.info != "namespace") return false;
context = context.prev;
}
}
CodeMirror.defineMode("clike", function(config, parserConfig) {
var indentUnit = config.indentUnit,
statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
dontAlignCalls = parserConfig.dontAlignCalls,
keywords = parserConfig.keywords || {},
types = parserConfig.types || {},
builtin = parserConfig.builtin || {},
blockKeywords = parserConfig.blockKeywords || {},
defKeywords = parserConfig.defKeywords || {},
atoms = parserConfig.atoms || {},
hooks = parserConfig.hooks || {},
multiLineStrings = parserConfig.multiLineStrings,
indentStatements = parserConfig.indentStatements !== false,
indentSwitch = parserConfig.indentSwitch !== false,
namespaceSeparator = parserConfig.namespaceSeparator,
isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/,
numberStart = parserConfig.numberStart || /[\d\.]/,
number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i,
isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/,
isIdentifierChar = parserConfig.isIdentifierChar || /[\w\$_\xa1-\uffff]/,
// An optional function that takes a {string} token and returns true if it
// should be treated as a builtin.
isReservedIdentifier = parserConfig.isReservedIdentifier || false;
var curPunc, isDefKeyword;
function tokenBase(stream, state) {
var ch = stream.next();
if (hooks[ch]) {
var result = hooks[ch](stream, state);
if (result !== false) return result;
}
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}
if (isPunctuationChar.test(ch)) {
curPunc = ch;
return null;
}
if (numberStart.test(ch)) {
stream.backUp(1)
if (stream.match(number)) return "number"
stream.next()
}
if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
}
if (stream.eat("/")) {
stream.skipToEnd();
return "comment";
}
}
if (isOperatorChar.test(ch)) {
while (!stream.match(/^\/[\/*]/, false) && stream.eat(isOperatorChar)) {}
return "operator";
}
stream.eatWhile(isIdentifierChar);
if (namespaceSeparator) while (stream.match(namespaceSeparator))
stream.eatWhile(isIdentifierChar);
var cur = stream.current();
if (contains(keywords, cur)) {
if (contains(blockKeywords, cur)) curPunc = "newstatement";
if (contains(defKeywords, cur)) isDefKeyword = true;
return "keyword";
}
if (contains(types, cur)) return "type";
if (contains(builtin, cur)
|| (isReservedIdentifier && isReservedIdentifier(cur))) {
if (contains(blockKeywords, cur)) curPunc = "newstatement";
return "builtin";
}
if (contains(atoms, cur)) return "atom";
return "variable";
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next, end = false;
while ((next = stream.next()) != null) {
if (next == quote && !escaped) {end = true; break;}
escaped = !escaped && next == "\\";
}
if (end || !(escaped || multiLineStrings))
state.tokenize = null;
return "string";
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = null;
break;
}
maybeEnd = (ch == "*");
}
return "comment";
}
function maybeEOL(stream, state) {
if (parserConfig.typeFirstDefinitions && stream.eol() && isTopScope(state.context))
state.typeAtEndOfLine = typeBefore(stream, state, stream.pos)
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: null,
context: new Context((basecolumn || 0) - indentUnit, 0, "top", null, false),
indented: 0,
startOfLine: true,
prevToken: null
};
},
token: function(stream, state) {
var ctx = state.context;
if (stream.sol()) {
if (ctx.align == null) ctx.align = false;
state.indented = stream.indentation();
state.startOfLine = true;
}
if (stream.eatSpace()) { maybeEOL(stream, state); return null; }
curPunc = isDefKeyword = null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style == "comment" || style == "meta") return style;
if (ctx.align == null) ctx.align = true;
if (curPunc == ";" || curPunc == ":" || (curPunc == "," && stream.match(/^\s*(?:\/\/.*)?$/, false)))
while (state.context.type == "statement") popContext(state);
else if (curPunc == "{") pushContext(state, stream.column(), "}");
else if (curPunc == "[") pushContext(state, stream.column(), "]");
else if (curPunc == "(") pushContext(state, stream.column(), ")");
else if (curPunc == "}") {
while (ctx.type == "statement") ctx = popContext(state);
if (ctx.type == "}") ctx = popContext(state);
while (ctx.type == "statement") ctx = popContext(state);
}
else if (curPunc == ctx.type) popContext(state);
else if (indentStatements &&
(((ctx.type == "}" || ctx.type == "top") && curPunc != ";") ||
(ctx.type == "statement" && curPunc == "newstatement"))) {
pushContext(state, stream.column(), "statement", stream.current());
}
if (style == "variable" &&
((state.prevToken == "def" ||
(parserConfig.typeFirstDefinitions && typeBefore(stream, state, stream.start) &&
isTopScope(state.context) && stream.match(/^\s*\(/, false)))))
style = "def";
if (hooks.token) {
var result = hooks.token(stream, state, style);
if (result !== undefined) style = result;
}
if (style == "def" && parserConfig.styleDefs === false) style = "variable";
state.startOfLine = false;
state.prevToken = isDefKeyword ? "def" : style || curPunc;
maybeEOL(stream, state);
return style;
},
indent: function(state, textAfter) {
if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine) return CodeMirror.Pass;
var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
var closing = firstChar == ctx.type;
if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
if (parserConfig.dontIndentStatements)
while (ctx.type == "statement" && parserConfig.dontIndentStatements.test(ctx.info))
ctx = ctx.prev
if (hooks.indent) {
var hook = hooks.indent(state, ctx, textAfter, indentUnit);
if (typeof hook == "number") return hook
}
var switchBlock = ctx.prev && ctx.prev.info == "switch";
if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) {
while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev
return ctx.indented
}
if (ctx.type == "statement")
return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
if (ctx.align && (!dontAlignCalls || ctx.type != ")"))
return ctx.column + (closing ? 0 : 1);
if (ctx.type == ")" && !closing)
return ctx.indented + statementIndentUnit;
return ctx.indented + (closing ? 0 : indentUnit) +
(!closing && switchBlock && !/^(?:case|default)\b/.test(textAfter) ? indentUnit : 0);
},
electricInput: indentSwitch ? /^\s*(?:case .*?:|default:|\{\}?|\})$/ : /^\s*[{}]$/,
blockCommentStart: "/*",
blockCommentEnd: "*/",
blockCommentContinue: " * ",
lineComment: "//",
fold: "brace"
};
});
function words(str) {
var obj = {}, words = str.split(" ");
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
function contains(words, word) {
if (typeof words === "function") {
return words(word);
} else {
return words.propertyIsEnumerable(word);
}
}
var cKeywords = "auto if break case register continue return default do sizeof " +
"static else struct switch extern typedef union for goto while enum const " +
"volatile inline restrict asm fortran";
// Keywords from https://en.cppreference.com/w/cpp/keyword includes C++20.
var cppKeywords = "alignas alignof and and_eq audit axiom bitand bitor catch " +
"class compl concept constexpr const_cast decltype delete dynamic_cast " +
"explicit export final friend import module mutable namespace new noexcept " +
"not not_eq operator or or_eq override private protected public " +
"reinterpret_cast requires static_assert static_cast template this " +
"thread_local throw try typeid typename using virtual xor xor_eq";
var objCKeywords = "bycopy byref in inout oneway out self super atomic nonatomic retain copy " +
"readwrite readonly strong weak assign typeof nullable nonnull null_resettable _cmd " +
"@interface @implementation @end @protocol @encode @property @synthesize @dynamic @class " +
"@public @package @private @protected @required @optional @try @catch @finally @import " +
"@selector @encode @defs @synchronized @autoreleasepool @compatibility_alias @available";
var objCBuiltins = "FOUNDATION_EXPORT FOUNDATION_EXTERN NS_INLINE NS_FORMAT_FUNCTION " +
" NS_RETURNS_RETAINEDNS_ERROR_ENUM NS_RETURNS_NOT_RETAINED NS_RETURNS_INNER_POINTER " +
"NS_DESIGNATED_INITIALIZER NS_ENUM NS_OPTIONS NS_REQUIRES_NIL_TERMINATION " +
"NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_SWIFT_NAME NS_REFINED_FOR_SWIFT"
// Do not use this. Use the cTypes function below. This is global just to avoid
// excessive calls when cTypes is being called multiple times during a parse.
var basicCTypes = words("int long char short double float unsigned signed " +
"void bool");
// Do not use this. Use the objCTypes function below. This is global just to avoid
// excessive calls when objCTypes is being called multiple times during a parse.
var basicObjCTypes = words("SEL instancetype id Class Protocol BOOL");
// Returns true if identifier is a "C" type.
// C type is defined as those that are reserved by the compiler (basicTypes),
// and those that end in _t (Reserved by POSIX for types)
// http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html
function cTypes(identifier) {
return contains(basicCTypes, identifier) || /.+_t$/.test(identifier);
}
// Returns true if identifier is a "Objective C" type.
function objCTypes(identifier) {
return cTypes(identifier) || contains(basicObjCTypes, identifier);
}
var cBlockKeywords = "case do else for if switch while struct enum union";
var cDefKeywords = "struct enum union";
function cppHook(stream, state) {
if (!state.startOfLine) return false
for (var ch, next = null; ch = stream.peek();) {
if (ch == "\\" && stream.match(/^.$/)) {
next = cppHook
break
} else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) {
break
}
stream.next()
}
state.tokenize = next
return "meta"
}
function pointerHook(_stream, state) {
if (state.prevToken == "type") return "type";
return false;
}
// For C and C++ (and ObjC): identifiers starting with __
// or _ followed by a capital letter are reserved for the compiler.
function cIsReservedIdentifier(token) {
if (!token || token.length < 2) return false;
if (token[0] != '_') return false;
return (token[1] == '_') || (token[1] !== token[1].toLowerCase());
}
function cpp14Literal(stream) {
stream.eatWhile(/[\w\.']/);
return "number";
}
function cpp11StringHook(stream, state) {
stream.backUp(1);
// Raw strings.
if (stream.match(/(R|u8R|uR|UR|LR)/)) {
var match = stream.match(/"([^\s\\()]{0,16})\(/);
if (!match) {
return false;
}
state.cpp11RawStringDelim = match[1];
state.tokenize = tokenRawString;
return tokenRawString(stream, state);
}
// Unicode strings/chars.
if (stream.match(/(u8|u|U|L)/)) {
if (stream.match(/["']/, /* eat */ false)) {
return "string";
}
return false;
}
// Ignore this hook.
stream.next();
return false;
}
function cppLooksLikeConstructor(word) {
var lastTwo = /(\w+)::~?(\w+)$/.exec(word);
return lastTwo && lastTwo[1] == lastTwo[2];
}
// C#-style strings where "" escapes a quote.
function tokenAtString(stream, state) {
var next;
while ((next = stream.next()) != null) {
if (next == '"' && !stream.eat('"')) {
state.tokenize = null;
break;
}
}
return "string";
}
// C++11 raw string literal is <prefix>"<delim>( anything )<delim>", where
// <delim> can be a string up to 16 characters long.
function tokenRawString(stream, state) {
// Escape characters that have special regex meanings.
var delim = state.cpp11RawStringDelim.replace(/[^\w\s]/g, '\\$&');
var match = stream.match(new RegExp(".*?\\)" + delim + '"'));
if (match)
state.tokenize = null;
else
stream.skipToEnd();
return "string";
}
function def(mimes, mode) {
if (typeof mimes == "string") mimes = [mimes];
var words = [];
function add(obj) {
if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop))
words.push(prop);
}
add(mode.keywords);
add(mode.types);
add(mode.builtin);
add(mode.atoms);
if (words.length) {
mode.helperType = mimes[0];
CodeMirror.registerHelper("hintWords", mimes[0], words);
}
for (var i = 0; i < mimes.length; ++i)
CodeMirror.defineMIME(mimes[i], mode);
}
def(["text/x-csrc", "text/x-c", "text/x-chdr"], {
name: "clike",
keywords: words(cKeywords),
types: cTypes,
blockKeywords: words(cBlockKeywords),
defKeywords: words(cDefKeywords),
typeFirstDefinitions: true,
atoms: words("NULL true false"),
isReservedIdentifier: cIsReservedIdentifier,
hooks: {
"#": cppHook,
"*": pointerHook,
},
modeProps: {fold: ["brace", "include"]}
});
def(["text/x-c++src", "text/x-c++hdr"], {
name: "clike",
keywords: words(cKeywords + " " + cppKeywords),
types: cTypes,
blockKeywords: words(cBlockKeywords + " class try catch"),
defKeywords: words(cDefKeywords + " class namespace"),
typeFirstDefinitions: true,
atoms: words("true false NULL nullptr"),
dontIndentStatements: /^template$/,
isIdentifierChar: /[\w\$_~\xa1-\uffff]/,
isReservedIdentifier: cIsReservedIdentifier,
hooks: {
"#": cppHook,
"*": pointerHook,
"u": cpp11StringHook,
"U": cpp11StringHook,
"L": cpp11StringHook,
"R": cpp11StringHook,
"0": cpp14Literal,
"1": cpp14Literal,
"2": cpp14Literal,
"3": cpp14Literal,
"4": cpp14Literal,
"5": cpp14Literal,
"6": cpp14Literal,
"7": cpp14Literal,
"8": cpp14Literal,
"9": cpp14Literal,
token: function(stream, state, style) {
if (style == "variable" && stream.peek() == "(" &&
(state.prevToken == ";" || state.prevToken == null ||
state.prevToken == "}") &&
cppLooksLikeConstructor(stream.current()))
return "def";
}
},
namespaceSeparator: "::",
modeProps: {fold: ["brace", "include"]}
});
def("text/x-java", {
name: "clike",
keywords: words("abstract assert break case catch class const continue default " +
"do else enum extends final finally for goto if implements import " +
"instanceof interface native new package private protected public " +
"return static strictfp super switch synchronized this throw throws transient " +
"try volatile while @interface"),
types: words("byte short int long float double boolean char void Boolean Byte Character Double Float " +
"Integer Long Number Object Short String StringBuffer StringBuilder Void"),
blockKeywords: words("catch class do else finally for if switch try while"),
defKeywords: words("class interface enum @interface"),
typeFirstDefinitions: true,
atoms: words("true false null"),
number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
hooks: {
"@": function(stream) {
// Don't match the @interface keyword.
if (stream.match('interface', false)) return false;
stream.eatWhile(/[\w\$_]/);
return "meta";
}
},
modeProps: {fold: ["brace", "import"]}
});
def("text/x-csharp", {
name: "clike",
keywords: words("abstract as async await base break case catch checked class const continue" +
" default delegate do else enum event explicit extern finally fixed for" +
" foreach goto if implicit in interface internal is lock namespace new" +
" operator out override params private protected public readonly ref return sealed" +
" sizeof stackalloc static struct switch this throw try typeof unchecked" +
" unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
" global group into join let orderby partial remove select set value var yield"),
types: words("Action Boolean Byte Char DateTime DateTimeOffset Decimal Double Func" +
" Guid Int16 Int32 Int64 Object SByte Single String Task TimeSpan UInt16 UInt32" +
" UInt64 bool byte char decimal double short int long object" +
" sbyte float string ushort uint ulong"),
blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
defKeywords: words("class interface namespace struct var"),
typeFirstDefinitions: true,
atoms: words("true false null"),
hooks: {
"@": function(stream, state) {
if (stream.eat('"')) {
state.tokenize = tokenAtString;
return tokenAtString(stream, state);
}
stream.eatWhile(/[\w\$_]/);
return "meta";
}
}
});
function tokenTripleString(stream, state) {
var escaped = false;
while (!stream.eol()) {
if (!escaped && stream.match('"""')) {
state.tokenize = null;
break;
}
escaped = stream.next() == "\\" && !escaped;
}
return "string";
}
function tokenNestedComment(depth) {
return function (stream, state) {
var ch
while (ch = stream.next()) {
if (ch == "*" && stream.eat("/")) {
if (depth == 1) {
state.tokenize = null
break
} else {
state.tokenize = tokenNestedComment(depth - 1)
return state.tokenize(stream, state)
}
} else if (ch == "/" && stream.eat("*")) {
state.tokenize = tokenNestedComment(depth + 1)
return state.tokenize(stream, state)
}
}
return "comment"
}
}
def("text/x-scala", {
name: "clike",
keywords: words(
/* scala */
"abstract case catch class def do else extends final finally for forSome if " +
"implicit import lazy match new null object override package private protected return " +
"sealed super this throw trait try type val var while with yield _ " +
/* package scala */
"assert assume require print println printf readLine readBoolean readByte readShort " +
"readChar readInt readLong readFloat readDouble"
),
types: words(
"AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " +
"Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable " +
"Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " +
"Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " +
"StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector " +
/* package java.lang */
"Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
"Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
"Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
"StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
),
multiLineStrings: true,
blockKeywords: words("catch class enum do else finally for forSome if match switch try while"),
defKeywords: words("class enum def object package trait type val var"),
atoms: words("true false null"),
indentStatements: false,
indentSwitch: false,
isOperatorChar: /[+\-*&%=<>!?|\/#:@]/,
hooks: {
"@": function(stream) {
stream.eatWhile(/[\w\$_]/);
return "meta";
},
'"': function(stream, state) {
if (!stream.match('""')) return false;
state.tokenize = tokenTripleString;
return state.tokenize(stream, state);
},
"'": function(stream) {
stream.eatWhile(/[\w\$_\xa1-\uffff]/);
return "atom";
},
"=": function(stream, state) {
var cx = state.context
if (cx.type == "}" && cx.align && stream.eat(">")) {
state.context = new Context(cx.indented, cx.column, cx.type, cx.info, null, cx.prev)
return "operator"
} else {
return false
}
},
"/": function(stream, state) {
if (!stream.eat("*")) return false
state.tokenize = tokenNestedComment(1)
return state.tokenize(stream, state)
}
},
modeProps: {closeBrackets: {pairs: '()[]{}""', triples: '"'}}
});
function tokenKotlinString(tripleString){
return function (stream, state) {
var escaped = false, next, end = false;
while (!stream.eol()) {
if (!tripleString && !escaped && stream.match('"') ) {end = true; break;}
if (tripleString && stream.match('"""')) {end = true; break;}
next = stream.next();
if(!escaped && next == "$" && stream.match('{'))
stream.skipTo("}");
escaped = !escaped && next == "\\" && !tripleString;
}
if (end || !tripleString)
state.tokenize = null;
return "string";
}
}
def("text/x-kotlin", {
name: "clike",
keywords: words(
/*keywords*/
"package as typealias class interface this super val operator " +
"var fun for is in This throw return annotation " +
"break continue object if else while do try when !in !is as? " +
/*soft keywords*/
"file import where by get set abstract enum open inner override private public internal " +
"protected catch finally out final vararg reified dynamic companion constructor init " +
"sealed field property receiver param sparam lateinit data inline noinline tailrec " +
"external annotation crossinline const operator infix suspend actual expect setparam"
),
types: words(
/* package java.lang */
"Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
"Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
"Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
"StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray " +
"ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy " +
"LazyThreadSafetyMode LongArray Nothing ShortArray Unit"
),
intendSwitch: false,
indentStatements: false,
multiLineStrings: true,
number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
blockKeywords: words("catch class do else finally for if where try while enum"),
defKeywords: words("class val var object interface fun"),
atoms: words("true false null this"),
hooks: {
"@": function(stream) {
stream.eatWhile(/[\w\$_]/);
return "meta";
},
'*': function(_stream, state) {
return state.prevToken == '.' ? 'variable' : 'operator';
},
'"': function(stream, state) {
state.tokenize = tokenKotlinString(stream.match('""'));
return state.tokenize(stream, state);
},
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenNestedComment(1);
return state.tokenize(stream, state)
},
indent: function(state, ctx, textAfter, indentUnit) {
var firstChar = textAfter && textAfter.charAt(0);
if ((state.prevToken == "}" || state.prevToken == ")") && textAfter == "")
return state.indented;
if ((state.prevToken == "operator" && textAfter != "}" && state.context.type != "}") ||
state.prevToken == "variable" && firstChar == "." ||
(state.prevToken == "}" || state.prevToken == ")") && firstChar == ".")
return indentUnit * 2 + ctx.indented;
if (ctx.align && ctx.type == "}")
return ctx.indented + (state.context.type == (textAfter || "").charAt(0) ? 0 : indentUnit);
}
},
modeProps: {closeBrackets: {triples: '"'}}
});
def(["x-shader/x-vertex", "x-shader/x-fragment"], {
name: "clike",
keywords: words("sampler1D sampler2D sampler3D samplerCube " +
"sampler1DShadow sampler2DShadow " +
"const attribute uniform varying " +
"break continue discard return " +
"for while do if else struct " +
"in out inout"),
types: words("float int bool void " +
"vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " +
"mat2 mat3 mat4"),
blockKeywords: words("for while do if else struct"),
builtin: words("radians degrees sin cos tan asin acos atan " +
"pow exp log exp2 sqrt inversesqrt " +
"abs sign floor ceil fract mod min max clamp mix step smoothstep " +
"length distance dot cross normalize ftransform faceforward " +
"reflect refract matrixCompMult " +
"lessThan lessThanEqual greaterThan greaterThanEqual " +
"equal notEqual any all not " +
"texture1D texture1DProj texture1DLod texture1DProjLod " +
"texture2D texture2DProj texture2DLod texture2DProjLod " +
"texture3D texture3DProj texture3DLod texture3DProjLod " +
"textureCube textureCubeLod " +
"shadow1D shadow2D shadow1DProj shadow2DProj " +
"shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod " +
"dFdx dFdy fwidth " +
"noise1 noise2 noise3 noise4"),
atoms: words("true false " +
"gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex " +
"gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 " +
"gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " +
"gl_FogCoord gl_PointCoord " +
"gl_Position gl_PointSize gl_ClipVertex " +
"gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor " +
"gl_TexCoord gl_FogFragCoord " +
"gl_FragCoord gl_FrontFacing " +
"gl_FragData gl_FragDepth " +
"gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " +
"gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " +
"gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " +
"gl_TexureMatrixTranspose gl_ModelViewMatrixInverseTranspose " +
"gl_ProjectionMatrixInverseTranspose " +
"gl_ModelViewProjectionMatrixInverseTranspose " +
"gl_TextureMatrixInverseTranspose " +
"gl_NormalScale gl_DepthRange gl_ClipPlane " +
"gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel " +
"gl_FrontLightModelProduct gl_BackLightModelProduct " +
"gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ " +
"gl_FogParameters " +
"gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords " +
"gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats " +
"gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " +
"gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " +
"gl_MaxDrawBuffers"),
indentSwitch: false,
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
});
def("text/x-nesc", {
name: "clike",
keywords: words(cKeywords + " as atomic async call command component components configuration event generic " +
"implementation includes interface module new norace nx_struct nx_union post provides " +
"signal task uses abstract extends"),
types: cTypes,
blockKeywords: words(cBlockKeywords),
atoms: words("null true false"),
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
});
def("text/x-objectivec", {
name: "clike",
keywords: words(cKeywords + " " + objCKeywords),
types: objCTypes,
builtin: words(objCBuiltins),
blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized"),
defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class"),
dontIndentStatements: /^@.*$/,
typeFirstDefinitions: true,
atoms: words("YES NO NULL Nil nil true false nullptr"),
isReservedIdentifier: cIsReservedIdentifier,
hooks: {
"#": cppHook,
"*": pointerHook,
},
modeProps: {fold: ["brace", "include"]}
});
def("text/x-objectivec++", {
name: "clike",
keywords: words(cKeywords + " " + objCKeywords + " " + cppKeywords),
types: objCTypes,
builtin: words(objCBuiltins),
blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized class try catch"),
defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class class namespace"),
dontIndentStatements: /^@.*$|^template$/,
typeFirstDefinitions: true,
atoms: words("YES NO NULL Nil nil true false nullptr"),
isReservedIdentifier: cIsReservedIdentifier,
hooks: {
"#": cppHook,
"*": pointerHook,
"u": cpp11StringHook,
"U": cpp11StringHook,
"L": cpp11StringHook,
"R": cpp11StringHook,
"0": cpp14Literal,
"1": cpp14Literal,
"2": cpp14Literal,
"3": cpp14Literal,
"4": cpp14Literal,
"5": cpp14Literal,
"6": cpp14Literal,
"7": cpp14Literal,
"8": cpp14Literal,
"9": cpp14Literal,
token: function(stream, state, style) {
if (style == "variable" && stream.peek() == "(" &&
(state.prevToken == ";" || state.prevToken == null ||
state.prevToken == "}") &&
cppLooksLikeConstructor(stream.current()))
return "def";
}
},
namespaceSeparator: "::",
modeProps: {fold: ["brace", "include"]}
});
def("text/x-squirrel", {
name: "clike",
keywords: words("base break clone continue const default delete enum extends function in class" +
" foreach local resume return this throw typeof yield constructor instanceof static"),
types: cTypes,
blockKeywords: words("case catch class else for foreach if switch try while"),
defKeywords: words("function local class"),
typeFirstDefinitions: true,
atoms: words("true false null"),
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
});
// Ceylon Strings need to deal with interpolation
var stringTokenizer = null;
function tokenCeylonString(type) {
return function(stream, state) {
var escaped = false, next, end = false;
while (!stream.eol()) {
if (!escaped && stream.match('"') &&
(type == "single" || stream.match('""'))) {
end = true;
break;
}
if (!escaped && stream.match('``')) {
stringTokenizer = tokenCeylonString(type);
end = true;
break;
}
next = stream.next();
escaped = type == "single" && !escaped && next == "\\";
}
if (end)
state.tokenize = null;
return "string";
}
}
def("text/x-ceylon", {
name: "clike",
keywords: words("abstracts alias assembly assert assign break case catch class continue dynamic else" +
" exists extends finally for function given if import in interface is let module new" +
" nonempty object of out outer package return satisfies super switch then this throw" +
" try value void while"),
types: function(word) {
// In Ceylon all identifiers that start with an uppercase are types
var first = word.charAt(0);
return (first === first.toUpperCase() && first !== first.toLowerCase());
},
blockKeywords: words("case catch class dynamic else finally for function if interface module new object switch try while"),
defKeywords: words("class dynamic function interface module object package value"),
builtin: words("abstract actual aliased annotation by default deprecated doc final formal late license" +
" native optional sealed see serializable shared suppressWarnings tagged throws variable"),
isPunctuationChar: /[\[\]{}\(\),;\:\.`]/,
isOperatorChar: /[+\-*&%=<>!?|^~:\/]/,
numberStart: /[\d#$]/,
number: /^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i,
multiLineStrings: true,
typeFirstDefinitions: true,
atoms: words("true false null larger smaller equal empty finished"),
indentSwitch: false,
styleDefs: false,
hooks: {
"@": function(stream) {
stream.eatWhile(/[\w\$_]/);
return "meta";
},
'"': function(stream, state) {
state.tokenize = tokenCeylonString(stream.match('""') ? "triple" : "single");
return state.tokenize(stream, state);
},
'`': function(stream, state) {
if (!stringTokenizer || !stream.match('`')) return false;
state.tokenize = stringTokenizer;
stringTokenizer = null;
return state.tokenize(stream, state);
},
"'": function(stream) {
stream.eatWhile(/[\w\$_\xa1-\uffff]/);
return "atom";
},
token: function(_stream, state, style) {
if ((style == "variable" || style == "type") &&
state.prevToken == ".") {
return "variable-2";
}
}
},
modeProps: {
fold: ["brace", "import"],
closeBrackets: {triples: '"'}
}
});
});
+831
View File
@@ -0,0 +1,831 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("css", function(config, parserConfig) {
var inline = parserConfig.inline
if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
var indentUnit = config.indentUnit,
tokenHooks = parserConfig.tokenHooks,
documentTypes = parserConfig.documentTypes || {},
mediaTypes = parserConfig.mediaTypes || {},
mediaFeatures = parserConfig.mediaFeatures || {},
mediaValueKeywords = parserConfig.mediaValueKeywords || {},
propertyKeywords = parserConfig.propertyKeywords || {},
nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
fontProperties = parserConfig.fontProperties || {},
counterDescriptors = parserConfig.counterDescriptors || {},
colorKeywords = parserConfig.colorKeywords || {},
valueKeywords = parserConfig.valueKeywords || {},
allowNested = parserConfig.allowNested,
lineComment = parserConfig.lineComment,
supportsAtComponent = parserConfig.supportsAtComponent === true;
var type, override;
function ret(style, tp) { type = tp; return style; }
// Tokenizers
function tokenBase(stream, state) {
var ch = stream.next();
if (tokenHooks[ch]) {
var result = tokenHooks[ch](stream, state);
if (result !== false) return result;
}
if (ch == "@") {
stream.eatWhile(/[\w\\\-]/);
return ret("def", stream.current());
} else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) {
return ret(null, "compare");
} else if (ch == "\"" || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "#") {
stream.eatWhile(/[\w\\\-]/);
return ret("atom", "hash");
} else if (ch == "!") {
stream.match(/^\s*\w*/);
return ret("keyword", "important");
} else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
} else if (ch === "-") {
if (/[\d.]/.test(stream.peek())) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
} else if (stream.match(/^-[\w\\\-]*/)) {
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
return ret("variable-2", "variable-definition");
return ret("variable-2", "variable");
} else if (stream.match(/^\w+-/)) {
return ret("meta", "meta");
}
} else if (/[,+>*\/]/.test(ch)) {
return ret(null, "select-op");
} else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
return ret("qualifier", "qualifier");
} else if (/[:;{}\[\]\(\)]/.test(ch)) {
return ret(null, ch);
} else if (stream.match(/[\w-.]+(?=\()/)) {
if (/^(url(-prefix)?|domain|regexp)$/.test(stream.current().toLowerCase())) {
state.tokenize = tokenParenthesized;
}
return ret("variable callee", "variable");
} else if (/[\w\\\-]/.test(ch)) {
stream.eatWhile(/[\w\\\-]/);
return ret("property", "word");
} else {
return ret(null, null);
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, ch;
while ((ch = stream.next()) != null) {
if (ch == quote && !escaped) {
if (quote == ")") stream.backUp(1);
break;
}
escaped = !escaped && ch == "\\";
}
if (ch == quote || !escaped && quote != ")") state.tokenize = null;
return ret("string", "string");
};
}
function tokenParenthesized(stream, state) {
stream.next(); // Must be '('
if (!stream.match(/\s*[\"\')]/, false))
state.tokenize = tokenString(")");
else
state.tokenize = null;
return ret(null, "(");
}
// Context management
function Context(type, indent, prev) {
this.type = type;
this.indent = indent;
this.prev = prev;
}
function pushContext(state, stream, type, indent) {
state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);
return type;
}
function popContext(state) {
if (state.context.prev)
state.context = state.context.prev;
return state.context.type;
}
function pass(type, stream, state) {
return states[state.context.type](type, stream, state);
}
function popAndPass(type, stream, state, n) {
for (var i = n || 1; i > 0; i--)
state.context = state.context.prev;
return pass(type, stream, state);
}
// Parser
function wordAsValue(stream) {
var word = stream.current().toLowerCase();
if (valueKeywords.hasOwnProperty(word))
override = "atom";
else if (colorKeywords.hasOwnProperty(word))
override = "keyword";
else
override = "variable";
}
var states = {};
states.top = function(type, stream, state) {
if (type == "{") {
return pushContext(state, stream, "block");
} else if (type == "}" && state.context.prev) {
return popContext(state);
} else if (supportsAtComponent && /@component/i.test(type)) {
return pushContext(state, stream, "atComponentBlock");
} else if (/^@(-moz-)?document$/i.test(type)) {
return pushContext(state, stream, "documentTypes");
} else if (/^@(media|supports|(-moz-)?document|import)$/i.test(type)) {
return pushContext(state, stream, "atBlock");
} else if (/^@(font-face|counter-style)/i.test(type)) {
state.stateArg = type;
return "restricted_atBlock_before";
} else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(type)) {
return "keyframes";
} else if (type && type.charAt(0) == "@") {
return pushContext(state, stream, "at");
} else if (type == "hash") {
override = "builtin";
} else if (type == "word") {
override = "tag";
} else if (type == "variable-definition") {
return "maybeprop";
} else if (type == "interpolation") {
return pushContext(state, stream, "interpolation");
} else if (type == ":") {
return "pseudo";
} else if (allowNested && type == "(") {
return pushContext(state, stream, "parens");
}
return state.context.type;
};
states.block = function(type, stream, state) {
if (type == "word") {
var word = stream.current().toLowerCase();
if (propertyKeywords.hasOwnProperty(word)) {
override = "property";
return "maybeprop";
} else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
override = "string-2";
return "maybeprop";
} else if (allowNested) {
override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag";
return "block";
} else {
override += " error";
return "maybeprop";
}
} else if (type == "meta") {
return "block";
} else if (!allowNested && (type == "hash" || type == "qualifier")) {
override = "error";
return "block";
} else {
return states.top(type, stream, state);
}
};
states.maybeprop = function(type, stream, state) {
if (type == ":") return pushContext(state, stream, "prop");
return pass(type, stream, state);
};
states.prop = function(type, stream, state) {
if (type == ";") return popContext(state);
if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
if (type == "}" || type == "{") return popAndPass(type, stream, state);
if (type == "(") return pushContext(state, stream, "parens");
if (type == "hash" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) {
override += " error";
} else if (type == "word") {
wordAsValue(stream);
} else if (type == "interpolation") {
return pushContext(state, stream, "interpolation");
}
return "prop";
};
states.propBlock = function(type, _stream, state) {
if (type == "}") return popContext(state);
if (type == "word") { override = "property"; return "maybeprop"; }
return state.context.type;
};
states.parens = function(type, stream, state) {
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == ")") return popContext(state);
if (type == "(") return pushContext(state, stream, "parens");
if (type == "interpolation") return pushContext(state, stream, "interpolation");
if (type == "word") wordAsValue(stream);
return "parens";
};
states.pseudo = function(type, stream, state) {
if (type == "meta") return "pseudo";
if (type == "word") {
override = "variable-3";
return state.context.type;
}
return pass(type, stream, state);
};
states.documentTypes = function(type, stream, state) {
if (type == "word" && documentTypes.hasOwnProperty(stream.current())) {
override = "tag";
return state.context.type;
} else {
return states.atBlock(type, stream, state);
}
};
states.atBlock = function(type, stream, state) {
if (type == "(") return pushContext(state, stream, "atBlock_parens");
if (type == "}" || type == ";") return popAndPass(type, stream, state);
if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
if (type == "interpolation") return pushContext(state, stream, "interpolation");
if (type == "word") {
var word = stream.current().toLowerCase();
if (word == "only" || word == "not" || word == "and" || word == "or")
override = "keyword";
else if (mediaTypes.hasOwnProperty(word))
override = "attribute";
else if (mediaFeatures.hasOwnProperty(word))
override = "property";
else if (mediaValueKeywords.hasOwnProperty(word))
override = "keyword";
else if (propertyKeywords.hasOwnProperty(word))
override = "property";
else if (nonStandardPropertyKeywords.hasOwnProperty(word))
override = "string-2";
else if (valueKeywords.hasOwnProperty(word))
override = "atom";
else if (colorKeywords.hasOwnProperty(word))
override = "keyword";
else
override = "error";
}
return state.context.type;
};
states.atComponentBlock = function(type, stream, state) {
if (type == "}")
return popAndPass(type, stream, state);
if (type == "{")
return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false);
if (type == "word")
override = "error";
return state.context.type;
};
states.atBlock_parens = function(type, stream, state) {
if (type == ")") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
return states.atBlock(type, stream, state);
};
states.restricted_atBlock_before = function(type, stream, state) {
if (type == "{")
return pushContext(state, stream, "restricted_atBlock");
if (type == "word" && state.stateArg == "@counter-style") {
override = "variable";
return "restricted_atBlock_before";
}
return pass(type, stream, state);
};
states.restricted_atBlock = function(type, stream, state) {
if (type == "}") {
state.stateArg = null;
return popContext(state);
}
if (type == "word") {
if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||
(state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))
override = "error";
else
override = "property";
return "maybeprop";
}
return "restricted_atBlock";
};
states.keyframes = function(type, stream, state) {
if (type == "word") { override = "variable"; return "keyframes"; }
if (type == "{") return pushContext(state, stream, "top");
return pass(type, stream, state);
};
states.at = function(type, stream, state) {
if (type == ";") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == "word") override = "tag";
else if (type == "hash") override = "builtin";
return "at";
};
states.interpolation = function(type, stream, state) {
if (type == "}") return popContext(state);
if (type == "{" || type == ";") return popAndPass(type, stream, state);
if (type == "word") override = "variable";
else if (type != "variable" && type != "(" && type != ")") override = "error";
return "interpolation";
};
return {
startState: function(base) {
return {tokenize: null,
state: inline ? "block" : "top",
stateArg: null,
context: new Context(inline ? "block" : "top", base || 0, null)};
},
token: function(stream, state) {
if (!state.tokenize && stream.eatSpace()) return null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style && typeof style == "object") {
type = style[1];
style = style[0];
}
override = style;
if (type != "comment")
state.state = states[state.state](type, stream, state);
return override;
},
indent: function(state, textAfter) {
var cx = state.context, ch = textAfter && textAfter.charAt(0);
var indent = cx.indent;
if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
if (cx.prev) {
if (ch == "}" && (cx.type == "block" || cx.type == "top" ||
cx.type == "interpolation" || cx.type == "restricted_atBlock")) {
// Resume indentation from parent context.
cx = cx.prev;
indent = cx.indent;
} else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
// Dedent relative to current context.
indent = Math.max(0, cx.indent - indentUnit);
}
}
return indent;
},
electricChars: "}",
blockCommentStart: "/*",
blockCommentEnd: "*/",
blockCommentContinue: " * ",
lineComment: lineComment,
fold: "brace"
};
});
function keySet(array) {
var keys = {};
for (var i = 0; i < array.length; ++i) {
keys[array[i].toLowerCase()] = true;
}
return keys;
}
var documentTypes_ = [
"domain", "regexp", "url", "url-prefix"
], documentTypes = keySet(documentTypes_);
var mediaTypes_ = [
"all", "aural", "braille", "handheld", "print", "projection", "screen",
"tty", "tv", "embossed"
], mediaTypes = keySet(mediaTypes_);
var mediaFeatures_ = [
"width", "min-width", "max-width", "height", "min-height", "max-height",
"device-width", "min-device-width", "max-device-width", "device-height",
"min-device-height", "max-device-height", "aspect-ratio",
"min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
"min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
"max-color", "color-index", "min-color-index", "max-color-index",
"monochrome", "min-monochrome", "max-monochrome", "resolution",
"min-resolution", "max-resolution", "scan", "grid", "orientation",
"device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio",
"pointer", "any-pointer", "hover", "any-hover"
], mediaFeatures = keySet(mediaFeatures_);
var mediaValueKeywords_ = [
"landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
"interlace", "progressive"
], mediaValueKeywords = keySet(mediaValueKeywords_);
var propertyKeywords_ = [
"align-content", "align-items", "align-self", "alignment-adjust",
"alignment-baseline", "anchor-point", "animation", "animation-delay",
"animation-direction", "animation-duration", "animation-fill-mode",
"animation-iteration-count", "animation-name", "animation-play-state",
"animation-timing-function", "appearance", "azimuth", "backface-visibility",
"background", "background-attachment", "background-blend-mode", "background-clip",
"background-color", "background-image", "background-origin", "background-position",
"background-repeat", "background-size", "baseline-shift", "binding",
"bleed", "bookmark-label", "bookmark-level", "bookmark-state",
"bookmark-target", "border", "border-bottom", "border-bottom-color",
"border-bottom-left-radius", "border-bottom-right-radius",
"border-bottom-style", "border-bottom-width", "border-collapse",
"border-color", "border-image", "border-image-outset",
"border-image-repeat", "border-image-slice", "border-image-source",
"border-image-width", "border-left", "border-left-color",
"border-left-style", "border-left-width", "border-radius", "border-right",
"border-right-color", "border-right-style", "border-right-width",
"border-spacing", "border-style", "border-top", "border-top-color",
"border-top-left-radius", "border-top-right-radius", "border-top-style",
"border-top-width", "border-width", "bottom", "box-decoration-break",
"box-shadow", "box-sizing", "break-after", "break-before", "break-inside",
"caption-side", "caret-color", "clear", "clip", "color", "color-profile", "column-count",
"column-fill", "column-gap", "column-rule", "column-rule-color",
"column-rule-style", "column-rule-width", "column-span", "column-width",
"columns", "content", "counter-increment", "counter-reset", "crop", "cue",
"cue-after", "cue-before", "cursor", "direction", "display",
"dominant-baseline", "drop-initial-after-adjust",
"drop-initial-after-align", "drop-initial-before-adjust",
"drop-initial-before-align", "drop-initial-size", "drop-initial-value",
"elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis",
"flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
"float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings",
"font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust",
"font-stretch", "font-style", "font-synthesis", "font-variant",
"font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
"font-variant-ligatures", "font-variant-numeric", "font-variant-position",
"font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
"grid-auto-rows", "grid-column", "grid-column-end", "grid-column-gap",
"grid-column-start", "grid-gap", "grid-row", "grid-row-end", "grid-row-gap",
"grid-row-start", "grid-template", "grid-template-areas", "grid-template-columns",
"grid-template-rows", "hanging-punctuation", "height", "hyphens",
"icon", "image-orientation", "image-rendering", "image-resolution",
"inline-box-align", "justify-content", "justify-items", "justify-self", "left", "letter-spacing",
"line-break", "line-height", "line-stacking", "line-stacking-ruby",
"line-stacking-shift", "line-stacking-strategy", "list-style",
"list-style-image", "list-style-position", "list-style-type", "margin",
"margin-bottom", "margin-left", "margin-right", "margin-top",
"marks", "marquee-direction", "marquee-loop",
"marquee-play-count", "marquee-speed", "marquee-style", "max-height",
"max-width", "min-height", "min-width", "mix-blend-mode", "move-to", "nav-down", "nav-index",
"nav-left", "nav-right", "nav-up", "object-fit", "object-position",
"opacity", "order", "orphans", "outline",
"outline-color", "outline-offset", "outline-style", "outline-width",
"overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
"padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
"page", "page-break-after", "page-break-before", "page-break-inside",
"page-policy", "pause", "pause-after", "pause-before", "perspective",
"perspective-origin", "pitch", "pitch-range", "place-content", "place-items", "place-self", "play-during", "position",
"presentation-level", "punctuation-trim", "quotes", "region-break-after",
"region-break-before", "region-break-inside", "region-fragment",
"rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness",
"right", "rotation", "rotation-point", "ruby-align", "ruby-overhang",
"ruby-position", "ruby-span", "shape-image-threshold", "shape-inside", "shape-margin",
"shape-outside", "size", "speak", "speak-as", "speak-header",
"speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set",
"tab-size", "table-layout", "target", "target-name", "target-new",
"target-position", "text-align", "text-align-last", "text-decoration",
"text-decoration-color", "text-decoration-line", "text-decoration-skip",
"text-decoration-style", "text-emphasis", "text-emphasis-color",
"text-emphasis-position", "text-emphasis-style", "text-height",
"text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow",
"text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position",
"text-wrap", "top", "transform", "transform-origin", "transform-style",
"transition", "transition-delay", "transition-duration",
"transition-property", "transition-timing-function", "unicode-bidi",
"user-select", "vertical-align", "visibility", "voice-balance", "voice-duration",
"voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
"voice-volume", "volume", "white-space", "widows", "width", "will-change", "word-break",
"word-spacing", "word-wrap", "z-index",
// SVG-specific
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
"flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
"color-interpolation", "color-interpolation-filters",
"color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
"marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
"stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
"stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
"baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
"glyph-orientation-vertical", "text-anchor", "writing-mode"
], propertyKeywords = keySet(propertyKeywords_);
var nonStandardPropertyKeywords_ = [
"scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
"scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
"scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside",
"searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
"searchfield-results-decoration", "zoom"
], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
var fontProperties_ = [
"font-family", "src", "unicode-range", "font-variant", "font-feature-settings",
"font-stretch", "font-weight", "font-style"
], fontProperties = keySet(fontProperties_);
var counterDescriptors_ = [
"additive-symbols", "fallback", "negative", "pad", "prefix", "range",
"speak-as", "suffix", "symbols", "system"
], counterDescriptors = keySet(counterDescriptors_);
var colorKeywords_ = [
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
"cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
"darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
"darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
"deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
"floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
"gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
"hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
"lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
"maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
"mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
"navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
"papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
"purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
"salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
"slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
"whitesmoke", "yellow", "yellowgreen"
], colorKeywords = keySet(colorKeywords_);
var valueKeywords_ = [
"above", "absolute", "activeborder", "additive", "activecaption", "afar",
"after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
"arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page",
"avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
"bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
"both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel",
"buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
"cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
"col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
"compact", "condensed", "contain", "content", "contents",
"content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
"cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
"decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
"destination-in", "destination-out", "destination-over", "devanagari", "difference",
"disc", "discard", "disclosure-closed", "disclosure-open", "document",
"dot-dash", "dot-dot-dash",
"dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
"element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
"ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
"ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
"ethiopic-halehame-gez", "ethiopic-halehame-om-et",
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
"ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
"forwards", "from", "geometricPrecision", "georgian", "graytext", "grid", "groove",
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
"help", "hidden", "hide", "higher", "highlight", "highlighttext",
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore",
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
"inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert",
"italic", "japanese-formal", "japanese-informal", "justify", "kannada",
"katakana", "katakana-iroha", "keep-all", "khmer",
"korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
"landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten",
"line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
"lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d",
"media-controls-background", "media-current-time-display",
"media-fullscreen-button", "media-mute-button", "media-play-button",
"media-return-to-realtime-button", "media-rewind-button",
"media-seek-back-button", "media-seek-forward-button", "media-slider",
"media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
"media-volume-slider-container", "media-volume-sliderthumb", "medium",
"menu", "menulist", "menulist-button", "menulist-text",
"menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
"mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize",
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
"ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote",
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
"outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
"painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter",
"pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d",
"progress", "push-button", "radial-gradient", "radio", "read-only",
"read-write", "read-write-plaintext-only", "rectangle", "region",
"relative", "repeat", "repeating-linear-gradient",
"repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse",
"rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
"rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
"s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
"scroll", "scrollbar", "scroll-position", "se-resize", "searchfield",
"searchfield-cancel-button", "searchfield-decoration",
"searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end",
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
"simp-chinese-formal", "simp-chinese-informal", "single",
"skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
"small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali",
"source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square",
"square-button", "start", "static", "status-bar", "stretch", "stroke", "sub",
"subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table",
"table-caption", "table-cell", "table-column", "table-column-group",
"table-footer-group", "table-header-group", "table-row", "table-row-group",
"tamil",
"telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
"trad-chinese-formal", "trad-chinese-informal", "transform",
"translate", "translate3d", "translateX", "translateY", "translateZ",
"transparent", "ultra-condensed", "ultra-expanded", "underline", "unset", "up",
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
"var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
"visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
"window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
"xx-large", "xx-small"
], valueKeywords = keySet(valueKeywords_);
var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)
.concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)
.concat(valueKeywords_);
CodeMirror.registerHelper("hintWords", "css", allWords);
function tokenCComment(stream, state) {
var maybeEnd = false, ch;
while ((ch = stream.next()) != null) {
if (maybeEnd && ch == "/") {
state.tokenize = null;
break;
}
maybeEnd = (ch == "*");
}
return ["comment", "comment"];
}
CodeMirror.defineMIME("text/css", {
documentTypes: documentTypes,
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
fontProperties: fontProperties,
counterDescriptors: counterDescriptors,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
tokenHooks: {
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}
},
name: "css"
});
CodeMirror.defineMIME("text/x-scss", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
allowNested: true,
lineComment: "//",
tokenHooks: {
"/": function(stream, state) {
if (stream.eat("/")) {
stream.skipToEnd();
return ["comment", "comment"];
} else if (stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
} else {
return ["operator", "operator"];
}
},
":": function(stream) {
if (stream.match(/\s*\{/, false))
return [null, null]
return false;
},
"$": function(stream) {
stream.match(/^[\w-]+/);
if (stream.match(/^\s*:/, false))
return ["variable-2", "variable-definition"];
return ["variable-2", "variable"];
},
"#": function(stream) {
if (!stream.eat("{")) return false;
return [null, "interpolation"];
}
},
name: "css",
helperType: "scss"
});
CodeMirror.defineMIME("text/x-less", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
allowNested: true,
lineComment: "//",
tokenHooks: {
"/": function(stream, state) {
if (stream.eat("/")) {
stream.skipToEnd();
return ["comment", "comment"];
} else if (stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
} else {
return ["operator", "operator"];
}
},
"@": function(stream) {
if (stream.eat("{")) return [null, "interpolation"];
if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/i, false)) return false;
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
return ["variable-2", "variable-definition"];
return ["variable-2", "variable"];
},
"&": function() {
return ["atom", "atom"];
}
},
name: "css",
helperType: "less"
});
CodeMirror.defineMIME("text/x-gss", {
documentTypes: documentTypes,
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
fontProperties: fontProperties,
counterDescriptors: counterDescriptors,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
supportsAtComponent: true,
tokenHooks: {
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}
},
name: "css",
helperType: "gss"
});
});
@@ -0,0 +1,152 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var defaultTags = {
script: [
["lang", /(javascript|babel)/i, "javascript"],
["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, "javascript"],
["type", /./, "text/plain"],
[null, null, "javascript"]
],
style: [
["lang", /^css$/i, "css"],
["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"],
["type", /./, "text/plain"],
[null, null, "css"]
]
};
function maybeBackup(stream, pat, style) {
var cur = stream.current(), close = cur.search(pat);
if (close > -1) {
stream.backUp(cur.length - close);
} else if (cur.match(/<\/?$/)) {
stream.backUp(cur.length);
if (!stream.match(pat, false)) stream.match(cur);
}
return style;
}
var attrRegexpCache = {};
function getAttrRegexp(attr) {
var regexp = attrRegexpCache[attr];
if (regexp) return regexp;
return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*");
}
function getAttrValue(text, attr) {
var match = text.match(getAttrRegexp(attr))
return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""
}
function getTagRegexp(tagName, anchored) {
return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i");
}
function addTags(from, to) {
for (var tag in from) {
var dest = to[tag] || (to[tag] = []);
var source = from[tag];
for (var i = source.length - 1; i >= 0; i--)
dest.unshift(source[i])
}
}
function findMatchingMode(tagInfo, tagText) {
for (var i = 0; i < tagInfo.length; i++) {
var spec = tagInfo[i];
if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2];
}
}
CodeMirror.defineMode("htmlmixed", function (config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {
name: "xml",
htmlMode: true,
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag
});
var tags = {};
var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;
addTags(defaultTags, tags);
if (configTags) addTags(configTags, tags);
if (configScript) for (var i = configScript.length - 1; i >= 0; i--)
tags.script.unshift(["type", configScript[i].matches, configScript[i].mode])
function html(stream, state) {
var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName
if (tag && !/[<>\s\/]/.test(stream.current()) &&
(tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) &&
tags.hasOwnProperty(tagName)) {
state.inTag = tagName + " "
} else if (state.inTag && tag && />$/.test(stream.current())) {
var inTag = /^([\S]+) (.*)/.exec(state.inTag)
state.inTag = null
var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2])
var mode = CodeMirror.getMode(config, modeSpec)
var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false);
state.token = function (stream, state) {
if (stream.match(endTagA, false)) {
state.token = html;
state.localState = state.localMode = null;
return null;
}
return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
};
state.localMode = mode;
state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "", ""));
} else if (state.inTag) {
state.inTag += stream.current()
if (stream.eol()) state.inTag += " "
}
return style;
};
return {
startState: function () {
var state = CodeMirror.startState(htmlMode);
return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};
},
copyState: function (state) {
var local;
if (state.localState) {
local = CodeMirror.copyState(state.localMode, state.localState);
}
return {token: state.token, inTag: state.inTag,
localMode: state.localMode, localState: local,
htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
},
token: function (stream, state) {
return state.token(stream, state);
},
indent: function (state, textAfter, line) {
if (!state.localMode || /^\s*<\//.test(textAfter))
return htmlMode.indent(state.htmlState, textAfter, line);
else if (state.localMode.indent)
return state.localMode.indent(state.localState, textAfter, line);
else
return CodeMirror.Pass;
},
innerMode: function (state) {
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
}
};
}, "xml", "javascript", "css");
CodeMirror.defineMIME("text/html", "htmlmixed");
});
@@ -0,0 +1,930 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
return {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
"await": C
};
}();
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (expressionAllowed(stream, state, 1)) {
readRegexp(stream);
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
return ret("regexp", "string-2");
} else {
stream.eat("=");
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
} else if (ch == "<" && stream.match("!--") || ch == "-" && stream.match("->")) {
stream.skipToEnd()
return ret("comment", "comment")
} else if (isOperatorChar.test(ch)) {
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
if (stream.eat("=")) {
if (ch == "!" || ch == "=") stream.eat("=")
} else if (/[<>*+\-]/.test(ch)) {
stream.eat(ch)
if (ch == ">") stream.eat(ch)
}
}
return ret("operator", "operator", stream.current());
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current()
if (state.lastType != ".") {
if (keywords.propertyIsEnumerable(word)) {
var kw = keywords[word]
return ret(kw.type, kw.style, word)
}
if (word == "async" && stream.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/, false))
return ret("async", "keyword", word)
}
return ret("variable", "variable", word)
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
state.tokenize = tokenBase;
return ret("jsonld-keyword", "meta");
}
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
if (m) arrow = m.index
}
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/`]/.test(ch)) {
for (;; --pos) {
if (pos == 0) return
var next = stream.string.charAt(pos - 1)
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
}
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function inList(name, list) {
for (var v = list; v; v = v.next) if (v.name == name) return true
return false;
}
function register(varname) {
var state = cx.state;
cx.marked = "def";
if (state.context) {
if (state.lexical.info == "var" && state.context && state.context.block) {
// FIXME function decls are also not block scoped
var newContext = registerVarScoped(varname, state.context)
if (newContext != null) {
state.context = newContext
return
}
} else if (!inList(varname, state.localVars)) {
state.localVars = new Var(varname, state.localVars)
return
}
}
// Fall through means this is global
if (parserConfig.globalVars && !inList(varname, state.globalVars))
state.globalVars = new Var(varname, state.globalVars)
}
function registerVarScoped(varname, context) {
if (!context) {
return null
} else if (context.block) {
var inner = registerVarScoped(varname, context.prev)
if (!inner) return null
if (inner == context.prev) return context
return new Context(inner, context.vars, true)
} else if (inList(varname, context.vars)) {
return context
} else {
return new Context(context.prev, new Var(varname, context.vars), false)
}
}
function isModifier(name) {
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
}
// Combinators
function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
function Var(name, next) { this.name = name; this.next = next }
var defaultVars = new Var("this", new Var("arguments", null))
function pushcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
cx.state.localVars = defaultVars
}
function pushblockcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
cx.state.localVars = null
}
function popcontext() {
cx.state.localVars = cx.state.context.vars
cx.state.context = cx.state.context.prev
}
popcontext.lex = true
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
else return cont(exp);
};
return exp;
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
if (type == "debugger") return cont(expect(";"));
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
if (type == ";") return cont();
if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()();
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "class" || (isTS && value == "interface")) {
cx.marked = "keyword"
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
}
if (type == "variable") {
if (isTS && value == "declare") {
cx.marked = "keyword"
return cont(statement)
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
cx.marked = "keyword"
if (value == "enum") return cont(enumdef);
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
} else if (isTS && value == "namespace") {
cx.marked = "keyword"
return cont(pushlex("form"), expression, statement, poplex)
} else if (isTS && value == "abstract") {
cx.marked = "keyword"
return cont(statement)
} else {
return cont(pushlex("stat"), maybelabel);
}
}
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
block, poplex, poplex, popcontext);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "async") return cont(statement)
if (value == "@") return cont(expression, statement)
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function maybeCatchBinding(type) {
if (type == "(") return cont(funarg, expect(")"))
}
function expression(type, value) {
return expressionInner(type, value, false);
}
function expressionNoComma(type, value) {
return expressionInner(type, value, true);
}
function parenExpr(type) {
if (type != "(") return pass()
return cont(pushlex(")"), expression, expect(")"), poplex)
}
function expressionInner(type, value, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop);
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
if (type == "quasi") return pass(quasi, maybeop);
if (type == "new") return cont(maybeTarget(noComma));
if (type == "import") return cont(expression);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(expression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
if (isTS && value == "<" && cx.stream.match(/^([^>]|<.*?>)*>\s*\(/, false))
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { return pass(quasi, me); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
if (type == "regexp") {
cx.state.lastType = cx.marked = "operator"
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
return cont(expr)
}
}
function quasi(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasi);
return cont(expression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasi);
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expressionNoComma);
}
function maybeTarget(noComma) {
return function(type) {
if (type == ".") return cont(noComma ? targetNoComma : target);
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
else return pass(noComma ? expressionNoComma : expression);
};
}
function target(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
}
function targetNoComma(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "async") {
cx.marked = "property";
return cont(objprop);
} else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
cx.state.fatArrowAt = cx.stream.pos + m[0].length
return cont(afterprop);
} else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (isTS && isModifier(value)) {
cx.marked = "keyword"
return cont(objprop)
} else if (type == "[") {
return cont(expression, maybetype, expect("]"), afterprop);
} else if (type == "spread") {
return cont(expressionNoComma, afterprop);
} else if (value == "*") {
cx.marked = "keyword";
return cont(objprop);
} else if (type == ":") {
return pass(afterprop)
}
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end, sep) {
function proceed(type, value) {
if (sep ? sep.indexOf(type) > -1 : type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(function(type, value) {
if (type == end || value == end) return pass()
return pass(what)
}, proceed);
}
if (type == end || value == end) return cont();
if (sep && sep.indexOf(";") > -1) return pass(what)
return cont(expect(end));
}
return function(type, value) {
if (type == end || value == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type, value) {
if (isTS) {
if (type == ":") return cont(typeexpr);
if (value == "?") return cont(maybetype);
}
}
function maybetypeOrIn(type, value) {
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
}
function mayberettype(type) {
if (isTS && type == ":") {
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
else return cont(typeexpr)
}
}
function isKW(_, value) {
if (value == "is") {
cx.marked = "keyword"
return cont()
}
}
function typeexpr(type, value) {
if (value == "keyof" || value == "typeof" || value == "infer") {
cx.marked = "keyword"
return cont(value == "typeof" ? expressionNoComma : typeexpr)
}
if (type == "variable" || value == "void") {
cx.marked = "type"
return cont(afterType)
}
if (value == "|" || value == "&") return cont(typeexpr)
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
}
function maybeReturnType(type) {
if (type == "=>") return cont(typeexpr)
}
function typeprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (value == "?" || type == "number" || type == "string") {
return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
} else if (type == "[") {
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
} else if (type == "(") {
return pass(functiondecl, typeprop)
}
}
function typearg(type, value) {
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
if (type == ":") return cont(typeexpr)
if (type == "spread") return cont(typearg)
return pass(typeexpr)
}
function afterType(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
if (type == "[") return cont(typeexpr, expect("]"), afterType)
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
}
function maybeTypeArgs(_, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
}
function typeparam() {
return pass(typeexpr, maybeTypeDefault)
}
function maybeTypeDefault(_, value) {
if (value == "=") return cont(typeexpr)
}
function vardef(_, value) {
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
if (type == "variable") { register(value); return cont(); }
if (type == "spread") return cont(pattern);
if (type == "[") return contCommasep(eltpattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
if (type == "spread") return cont(pattern);
if (type == "}") return pass();
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
return cont(expect(":"), pattern, maybeAssign);
}
function eltpattern() {
return pass(pattern, maybeAssign)
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
}
function forspec(type, value) {
if (value == "await") return cont(forspec);
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, forspec2);
if (type == "variable") return cont(forspec2);
return pass(forspec2)
}
function forspec2(type, value) {
if (type == ")") return cont()
if (type == ";") return cont(forspec2)
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
return pass(expression, forspec2)
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
}
function functiondecl(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
if (type == "variable") {register(value); return cont(functiondecl);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
}
function typename(type, value) {
if (type == "keyword" || type == "variable") {
cx.marked = "type"
return cont(typename)
} else if (value == "<") {
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
}
}
function funarg(type, value) {
if (value == "@") cont(expression, funarg)
if (type == "spread") return cont(funarg);
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
if (isTS && type == "this") return cont(maybetype, maybeAssign)
return pass(pattern, maybetype, maybeAssign);
}
function classExpression(type, value) {
// Class expressions may have an optional name.
if (type == "variable") return className(type, value);
return classNameAfter(type, value);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
if (value == "implements") cx.marked = "keyword";
return cont(isTS ? typeexpr : expression, classNameAfter);
}
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
function classBody(type, value) {
if (type == "async" ||
(type == "variable" &&
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) {
cx.marked = "keyword";
return cont(classBody);
}
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
return cont(isTS ? classfield : functiondef, classBody);
}
if (type == "number" || type == "string") return cont(isTS ? classfield : functiondef, classBody);
if (type == "[")
return cont(expression, maybetype, expect("]"), isTS ? classfield : functiondef, classBody)
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (isTS && type == "(") return pass(functiondecl, classBody)
if (type == ";" || type == ",") return cont(classBody);
if (type == "}") return cont();
if (value == "@") return cont(expression, classBody)
}
function classfield(type, value) {
if (value == "?") return cont(classfield)
if (type == ":") return cont(typeexpr, maybeAssign)
if (value == "=") return cont(expressionNoComma)
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
return pass(isInterface ? functiondecl : functiondef)
}
function afterExport(type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
return pass(statement);
}
function exportField(type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
if (type == "variable") return pass(expressionNoComma, exportField);
}
function afterImport(type) {
if (type == "string") return cont();
if (type == "(") return pass(expression);
return pass(importSpec, maybeMoreImports, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
if (value == "*") cx.marked = "keyword";
return cont(maybeAs);
}
function maybeMoreImports(type) {
if (type == ",") return cont(importSpec, maybeMoreImports)
}
function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(commasep(expressionNoComma, "]"));
}
function enumdef() {
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
}
function enummember() {
return pass(pattern, maybeAssign);
}
function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}
function expressionAllowed(stream, state, backUp) {
return state.tokenize == tokenBase &&
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && new Context(null, null, false),
indented: basecolumn || 0
};
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
// Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break;
}
while ((lexical.type == "stat" || lexical.type == "form") &&
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
blockCommentContinue: jsonMode ? null : " * ",
lineComment: jsonMode ? null : "//",
fold: "brace",
closeBrackets: "()[]{}''\"\"``",
helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
jsonMode: jsonMode,
expressionAllowed: expressionAllowed,
skipExpression: function(state) {
var top = state.cc[state.cc.length - 1]
if (top == expression || top == expressionNoComma) state.cc.pop()
}
};
});
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
});
File diff suppressed because one or more lines are too long
+225
View File
@@ -0,0 +1,225 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
/**
* Smarty 2 and 3 mode.
*/
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("smarty", function(config, parserConf) {
var rightDelimiter = parserConf.rightDelimiter || "}";
var leftDelimiter = parserConf.leftDelimiter || "{";
var version = parserConf.version || 2;
var baseMode = CodeMirror.getMode(config, parserConf.baseMode || "null");
var keyFunctions = ["debug", "extends", "function", "include", "literal"];
var regs = {
operatorChars: /[+\-*&%=<>!?]/,
validIdentifier: /[a-zA-Z0-9_]/,
stringChar: /['"]/
};
var last;
function cont(style, lastType) {
last = lastType;
return style;
}
function chain(stream, state, parser) {
state.tokenize = parser;
return parser(stream, state);
}
// Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode
function doesNotCount(stream, pos) {
if (pos == null) pos = stream.pos;
return version === 3 && leftDelimiter == "{" &&
(pos == stream.string.length || /\s/.test(stream.string.charAt(pos)));
}
function tokenTop(stream, state) {
var string = stream.string;
for (var scan = stream.pos;;) {
var nextMatch = string.indexOf(leftDelimiter, scan);
scan = nextMatch + leftDelimiter.length;
if (nextMatch == -1 || !doesNotCount(stream, nextMatch + leftDelimiter.length)) break;
}
if (nextMatch == stream.pos) {
stream.match(leftDelimiter);
if (stream.eat("*")) {
return chain(stream, state, tokenBlock("comment", "*" + rightDelimiter));
} else {
state.depth++;
state.tokenize = tokenSmarty;
last = "startTag";
return "tag";
}
}
if (nextMatch > -1) stream.string = string.slice(0, nextMatch);
var token = baseMode.token(stream, state.base);
if (nextMatch > -1) stream.string = string;
return token;
}
// parsing Smarty content
function tokenSmarty(stream, state) {
if (stream.match(rightDelimiter, true)) {
if (version === 3) {
state.depth--;
if (state.depth <= 0) {
state.tokenize = tokenTop;
}
} else {
state.tokenize = tokenTop;
}
return cont("tag", null);
}
if (stream.match(leftDelimiter, true)) {
state.depth++;
return cont("tag", "startTag");
}
var ch = stream.next();
if (ch == "$") {
stream.eatWhile(regs.validIdentifier);
return cont("variable-2", "variable");
} else if (ch == "|") {
return cont("operator", "pipe");
} else if (ch == ".") {
return cont("operator", "property");
} else if (regs.stringChar.test(ch)) {
state.tokenize = tokenAttribute(ch);
return cont("string", "string");
} else if (regs.operatorChars.test(ch)) {
stream.eatWhile(regs.operatorChars);
return cont("operator", "operator");
} else if (ch == "[" || ch == "]") {
return cont("bracket", "bracket");
} else if (ch == "(" || ch == ")") {
return cont("bracket", "operator");
} else if (/\d/.test(ch)) {
stream.eatWhile(/\d/);
return cont("number", "number");
} else {
if (state.last == "variable") {
if (ch == "@") {
stream.eatWhile(regs.validIdentifier);
return cont("property", "property");
} else if (ch == "|") {
stream.eatWhile(regs.validIdentifier);
return cont("qualifier", "modifier");
}
} else if (state.last == "pipe") {
stream.eatWhile(regs.validIdentifier);
return cont("qualifier", "modifier");
} else if (state.last == "whitespace") {
stream.eatWhile(regs.validIdentifier);
return cont("attribute", "modifier");
} if (state.last == "property") {
stream.eatWhile(regs.validIdentifier);
return cont("property", null);
} else if (/\s/.test(ch)) {
last = "whitespace";
return null;
}
var str = "";
if (ch != "/") {
str += ch;
}
var c = null;
while (c = stream.eat(regs.validIdentifier)) {
str += c;
}
for (var i=0, j=keyFunctions.length; i<j; i++) {
if (keyFunctions[i] == str) {
return cont("keyword", "keyword");
}
}
if (/\s/.test(ch)) {
return null;
}
return cont("tag", "tag");
}
}
function tokenAttribute(quote) {
return function(stream, state) {
var prevChar = null;
var currChar = null;
while (!stream.eol()) {
currChar = stream.peek();
if (stream.next() == quote && prevChar !== '\\') {
state.tokenize = tokenSmarty;
break;
}
prevChar = currChar;
}
return "string";
};
}
function tokenBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = tokenTop;
break;
}
stream.next();
}
return style;
};
}
return {
startState: function() {
return {
base: CodeMirror.startState(baseMode),
tokenize: tokenTop,
last: null,
depth: 0
};
},
copyState: function(state) {
return {
base: CodeMirror.copyState(baseMode, state.base),
tokenize: state.tokenize,
last: state.last,
depth: state.depth
};
},
innerMode: function(state) {
if (state.tokenize == tokenTop)
return {mode: baseMode, state: state.base};
},
token: function(stream, state) {
var style = state.tokenize(stream, state);
state.last = last;
return style;
},
indent: function(state, text, line) {
if (state.tokenize == tokenTop && baseMode.indent)
return baseMode.indent(state.base, text, line);
else
return CodeMirror.Pass;
},
blockCommentStart: leftDelimiter + "*",
blockCommentEnd: "*" + rightDelimiter
};
});
CodeMirror.defineMIME("text/x-smarty", "smarty");
});
@@ -0,0 +1,173 @@
/**
* @file smartymixed.js
* @brief Smarty Mixed Codemirror mode (Smarty + Mixed HTML)
* @author Ruslan Osmanov <rrosmanov at gmail dot com>
* @version 3.0
* @date 05.07.2013
*/
CodeMirror.defineMode("smartymixed", function(config) {
var settings, regs, helpers, parsers,
htmlMixedMode = CodeMirror.getMode(config, "htmlmixed"),
smartyMode = CodeMirror.getMode(config, "smarty"),
settings = {
rightDelimiter: '}',
leftDelimiter: '{'
};
if (config.hasOwnProperty("leftDelimiter")) {
settings.leftDelimiter = config.leftDelimiter;
}
if (config.hasOwnProperty("rightDelimiter")) {
settings.rightDelimiter = config.rightDelimiter;
}
regs = {
smartyComment: new RegExp("^" + settings.leftDelimiter + "\\*"),
literalOpen: new RegExp(settings.leftDelimiter + "literal" + settings.rightDelimiter),
literalClose: new RegExp(settings.leftDelimiter + "\/literal" + settings.rightDelimiter),
hasLeftDelimeter: new RegExp(".*" + settings.leftDelimiter),
htmlHasLeftDelimeter: new RegExp("[^<>]*" + settings.leftDelimiter)
};
helpers = {
chain: function(stream, state, parser) {
state.tokenize = parser;
return parser(stream, state);
},
cleanChain: function(stream, state, parser) {
state.tokenize = null;
state.localState = null;
state.localMode = null;
return (typeof parser == "string") ? (parser ? parser : null) : parser(stream, state);
},
maybeBackup: function(stream, pat, style) {
var cur = stream.current();
var close = cur.search(pat),
m;
if (close > - 1) stream.backUp(cur.length - close);
else if (m = cur.match(/<\/?$/)) {
stream.backUp(cur.length);
if (!stream.match(pat, false)) stream.match(cur[0]);
}
return style;
}
};
parsers = {
html: function(stream, state) {
if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false) && state.htmlMixedState.htmlState.tagName === null) {
state.tokenize = parsers.smarty;
state.localMode = smartyMode;
state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
} else if (!state.inLiteral && stream.match(settings.leftDelimiter, false)) {
state.tokenize = parsers.smarty;
state.localMode = smartyMode;
state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
}
return htmlMixedMode.token(stream, state.htmlMixedState);
},
smarty: function(stream, state) {
if (stream.match(settings.leftDelimiter, false)) {
if (stream.match(regs.smartyComment, false)) {
return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter));
}
} else if (stream.match(settings.rightDelimiter, false)) {
stream.eat(settings.rightDelimiter);
state.tokenize = parsers.html;
state.localMode = htmlMixedMode;
state.localState = state.htmlMixedState;
return "tag";
}
return helpers.maybeBackup(stream, settings.rightDelimiter, smartyMode.token(stream, state.localState));
},
inBlock: function(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
helpers.cleanChain(stream, state, "");
break;
}
stream.next();
}
return style;
};
}
};
return {
startState: function() {
var state = htmlMixedMode.startState();
return {
token: parsers.html,
localMode: null,
localState: null,
htmlMixedState: state,
tokenize: null,
inLiteral: false
};
},
copyState: function(state) {
var local = null, tok = (state.tokenize || state.token);
if (state.localState) {
local = CodeMirror.copyState((tok != parsers.html ? smartyMode : htmlMixedMode), state.localState);
}
return {
token: state.token,
tokenize: state.tokenize,
localMode: state.localMode,
localState: local,
htmlMixedState: CodeMirror.copyState(htmlMixedMode, state.htmlMixedState),
inLiteral: state.inLiteral
};
},
token: function(stream, state) {
if (stream.match(settings.leftDelimiter, false)) {
if (!state.inLiteral && stream.match(regs.literalOpen, true)) {
state.inLiteral = true;
return "keyword";
} else if (state.inLiteral && stream.match(regs.literalClose, true)) {
state.inLiteral = false;
return "keyword";
}
}
if (state.inLiteral && state.localState != state.htmlMixedState) {
state.tokenize = parsers.html;
state.localMode = htmlMixedMode;
state.localState = state.htmlMixedState;
}
var style = (state.tokenize || state.token)(stream, state);
return style;
},
indent: function(state, textAfter) {
if (state.localMode == smartyMode
|| (state.inLiteral && !state.localMode)
|| regs.hasLeftDelimeter.test(textAfter)) {
return CodeMirror.Pass;
}
return htmlMixedMode.indent(state.htmlMixedState, textAfter);
},
innerMode: function(state) {
return {
state: state.localState || state.htmlMixedState,
mode: state.localMode || htmlMixedMode
};
}
};
},
"htmlmixed");
CodeMirror.defineMIME("text/x-smarty", "smartymixed");
// vim: et ts=2 sts=2 sw=2
File diff suppressed because one or more lines are too long
+413
View File
@@ -0,0 +1,413 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var htmlConfig = {
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
'track': true, 'wbr': true, 'menuitem': true},
implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
'th': true, 'tr': true},
contextGrabbers: {
'dd': {'dd': true, 'dt': true},
'dt': {'dd': true, 'dt': true},
'li': {'li': true},
'option': {'option': true, 'optgroup': true},
'optgroup': {'optgroup': true},
'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
'rp': {'rp': true, 'rt': true},
'rt': {'rp': true, 'rt': true},
'tbody': {'tbody': true, 'tfoot': true},
'td': {'td': true, 'th': true},
'tfoot': {'tbody': true},
'th': {'td': true, 'th': true},
'thead': {'tbody': true, 'tfoot': true},
'tr': {'tr': true}
},
doNotIndent: {"pre": true},
allowUnquoted: true,
allowMissing: true,
caseFold: true
}
var xmlConfig = {
autoSelfClosers: {},
implicitlyClosed: {},
contextGrabbers: {},
doNotIndent: {},
allowUnquoted: false,
allowMissing: false,
allowMissingTagName: false,
caseFold: false
}
CodeMirror.defineMode("xml", function(editorConf, config_) {
var indentUnit = editorConf.indentUnit
var config = {}
var defaults = config_.htmlMode ? htmlConfig : xmlConfig
for (var prop in defaults) config[prop] = defaults[prop]
for (var prop in config_) config[prop] = config_[prop]
// Return variables for tokenizers
var type, setStyle;
function inText(stream, state) {
function chain(parser) {
state.tokenize = parser;
return parser(stream, state);
}
var ch = stream.next();
if (ch == "<") {
if (stream.eat("!")) {
if (stream.eat("[")) {
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
else return null;
} else if (stream.match("--")) {
return chain(inBlock("comment", "-->"));
} else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
return chain(doctype(1));
} else {
return null;
}
} else if (stream.eat("?")) {
stream.eatWhile(/[\w\._\-]/);
state.tokenize = inBlock("meta", "?>");
return "meta";
} else {
type = stream.eat("/") ? "closeTag" : "openTag";
state.tokenize = inTag;
return "tag bracket";
}
} else if (ch == "&") {
var ok;
if (stream.eat("#")) {
if (stream.eat("x")) {
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
} else {
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
}
} else {
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
}
return ok ? "atom" : "error";
} else {
stream.eatWhile(/[^&<]/);
return null;
}
}
inText.isInText = true;
function inTag(stream, state) {
var ch = stream.next();
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
state.tokenize = inText;
type = ch == ">" ? "endTag" : "selfcloseTag";
return "tag bracket";
} else if (ch == "=") {
type = "equals";
return null;
} else if (ch == "<") {
state.tokenize = inText;
state.state = baseState;
state.tagName = state.tagStart = null;
var next = state.tokenize(stream, state);
return next ? next + " tag error" : "tag error";
} else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
state.stringStartCol = stream.column();
return state.tokenize(stream, state);
} else {
stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
return "word";
}
}
function inAttribute(quote) {
var closure = function(stream, state) {
while (!stream.eol()) {
if (stream.next() == quote) {
state.tokenize = inTag;
break;
}
}
return "string";
};
closure.isInAttribute = true;
return closure;
}
function inBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = inText;
break;
}
stream.next();
}
return style;
}
}
function doctype(depth) {
return function(stream, state) {
var ch;
while ((ch = stream.next()) != null) {
if (ch == "<") {
state.tokenize = doctype(depth + 1);
return state.tokenize(stream, state);
} else if (ch == ">") {
if (depth == 1) {
state.tokenize = inText;
break;
} else {
state.tokenize = doctype(depth - 1);
return state.tokenize(stream, state);
}
}
}
return "meta";
};
}
function Context(state, tagName, startOfLine) {
this.prev = state.context;
this.tagName = tagName;
this.indent = state.indented;
this.startOfLine = startOfLine;
if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
this.noIndent = true;
}
function popContext(state) {
if (state.context) state.context = state.context.prev;
}
function maybePopContext(state, nextTagName) {
var parentTagName;
while (true) {
if (!state.context) {
return;
}
parentTagName = state.context.tagName;
if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||
!config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext(state);
}
}
function baseState(type, stream, state) {
if (type == "openTag") {
state.tagStart = stream.column();
return tagNameState;
} else if (type == "closeTag") {
return closeTagNameState;
} else {
return baseState;
}
}
function tagNameState(type, stream, state) {
if (type == "word") {
state.tagName = stream.current();
setStyle = "tag";
return attrState;
} else if (config.allowMissingTagName && type == "endTag") {
setStyle = "tag bracket";
return attrState(type, stream, state);
} else {
setStyle = "error";
return tagNameState;
}
}
function closeTagNameState(type, stream, state) {
if (type == "word") {
var tagName = stream.current();
if (state.context && state.context.tagName != tagName &&
config.implicitlyClosed.hasOwnProperty(state.context.tagName))
popContext(state);
if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
setStyle = "tag";
return closeState;
} else {
setStyle = "tag error";
return closeStateErr;
}
} else if (config.allowMissingTagName && type == "endTag") {
setStyle = "tag bracket";
return closeState(type, stream, state);
} else {
setStyle = "error";
return closeStateErr;
}
}
function closeState(type, _stream, state) {
if (type != "endTag") {
setStyle = "error";
return closeState;
}
popContext(state);
return baseState;
}
function closeStateErr(type, stream, state) {
setStyle = "error";
return closeState(type, stream, state);
}
function attrState(type, _stream, state) {
if (type == "word") {
setStyle = "attribute";
return attrEqState;
} else if (type == "endTag" || type == "selfcloseTag") {
var tagName = state.tagName, tagStart = state.tagStart;
state.tagName = state.tagStart = null;
if (type == "selfcloseTag" ||
config.autoSelfClosers.hasOwnProperty(tagName)) {
maybePopContext(state, tagName);
} else {
maybePopContext(state, tagName);
state.context = new Context(state, tagName, tagStart == state.indented);
}
return baseState;
}
setStyle = "error";
return attrState;
}
function attrEqState(type, stream, state) {
if (type == "equals") return attrValueState;
if (!config.allowMissing) setStyle = "error";
return attrState(type, stream, state);
}
function attrValueState(type, stream, state) {
if (type == "string") return attrContinuedState;
if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
setStyle = "error";
return attrState(type, stream, state);
}
function attrContinuedState(type, stream, state) {
if (type == "string") return attrContinuedState;
return attrState(type, stream, state);
}
return {
startState: function(baseIndent) {
var state = {tokenize: inText,
state: baseState,
indented: baseIndent || 0,
tagName: null, tagStart: null,
context: null}
if (baseIndent != null) state.baseIndent = baseIndent
return state
},
token: function(stream, state) {
if (!state.tagName && stream.sol())
state.indented = stream.indentation();
if (stream.eatSpace()) return null;
type = null;
var style = state.tokenize(stream, state);
if ((style || type) && style != "comment") {
setStyle = null;
state.state = state.state(type || style, stream, state);
if (setStyle)
style = setStyle == "error" ? style + " error" : setStyle;
}
return style;
},
indent: function(state, textAfter, fullLine) {
var context = state.context;
// Indent multi-line strings (e.g. css).
if (state.tokenize.isInAttribute) {
if (state.tagStart == state.indented)
return state.stringStartCol + 1;
else
return state.indented + indentUnit;
}
if (context && context.noIndent) return CodeMirror.Pass;
if (state.tokenize != inTag && state.tokenize != inText)
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
// Indent the starts of attribute names.
if (state.tagName) {
if (config.multilineTagIndentPastTag !== false)
return state.tagStart + state.tagName.length + 2;
else
return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
}
if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
if (tagAfter && tagAfter[1]) { // Closing tag spotted
while (context) {
if (context.tagName == tagAfter[2]) {
context = context.prev;
break;
} else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {
context = context.prev;
} else {
break;
}
}
} else if (tagAfter) { // Opening tag spotted
while (context) {
var grabbers = config.contextGrabbers[context.tagName];
if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
context = context.prev;
else
break;
}
}
while (context && context.prev && !context.startOfLine)
context = context.prev;
if (context) return context.indent + indentUnit;
else return state.baseIndent || 0;
},
electricInput: /<\/[\s\w:]+>$/,
blockCommentStart: "<!--",
blockCommentEnd: "-->",
configuration: config.htmlMode ? "html" : "xml",
helperType: config.htmlMode ? "html" : "xml",
skipAttribute: function(state) {
if (state.state == attrValueState)
state.state = attrState
},
xmlCurrentTag: function(state) {
return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null
},
xmlCurrentContext: function(state) {
var context = []
for (var cx = state.context; cx; cx = cx.prev)
if (cx.tagName) context.push(cx.tagName)
return context.reverse()
}
};
});
CodeMirror.defineMIME("text/xml", "xml");
CodeMirror.defineMIME("application/xml", "xml");
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
});
+41
View File
@@ -0,0 +1,41 @@
/*
Name: 3024 day
Author: Jan T. Sott (http://github.com/idleberg)
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
*/
.cm-s-3024-day.CodeMirror { background: #f7f7f7; color: #3a3432; }
.cm-s-3024-day div.CodeMirror-selected { background: #d6d5d4; }
.cm-s-3024-day .CodeMirror-line::selection, .cm-s-3024-day .CodeMirror-line > span::selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d6d5d4; }
.cm-s-3024-day .CodeMirror-line::-moz-selection, .cm-s-3024-day .CodeMirror-line > span::-moz-selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d9d9d9; }
.cm-s-3024-day .CodeMirror-gutters { background: #f7f7f7; border-right: 0px; }
.cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20; }
.cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c; }
.cm-s-3024-day .CodeMirror-linenumber { color: #807d7c; }
.cm-s-3024-day .CodeMirror-cursor { border-left: 1px solid #5c5855; }
.cm-s-3024-day span.cm-comment { color: #cdab53; }
.cm-s-3024-day span.cm-atom { color: #a16a94; }
.cm-s-3024-day span.cm-number { color: #a16a94; }
.cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute { color: #01a252; }
.cm-s-3024-day span.cm-keyword { color: #db2d20; }
.cm-s-3024-day span.cm-string { color: #fded02; }
.cm-s-3024-day span.cm-variable { color: #01a252; }
.cm-s-3024-day span.cm-variable-2 { color: #01a0e4; }
.cm-s-3024-day span.cm-def { color: #e8bbd0; }
.cm-s-3024-day span.cm-bracket { color: #3a3432; }
.cm-s-3024-day span.cm-tag { color: #db2d20; }
.cm-s-3024-day span.cm-link { color: #a16a94; }
.cm-s-3024-day span.cm-error { background: #db2d20; color: #5c5855; }
.cm-s-3024-day .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: #a16a94 !important; }
+39
View File
@@ -0,0 +1,39 @@
/*
Name: 3024 night
Author: Jan T. Sott (http://github.com/idleberg)
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
*/
.cm-s-3024-night.CodeMirror { background: #090300; color: #d6d5d4; }
.cm-s-3024-night div.CodeMirror-selected { background: #3a3432; }
.cm-s-3024-night .CodeMirror-line::selection, .cm-s-3024-night .CodeMirror-line > span::selection, .cm-s-3024-night .CodeMirror-line > span > span::selection { background: rgba(58, 52, 50, .99); }
.cm-s-3024-night .CodeMirror-line::-moz-selection, .cm-s-3024-night .CodeMirror-line > span::-moz-selection, .cm-s-3024-night .CodeMirror-line > span > span::-moz-selection { background: rgba(58, 52, 50, .99); }
.cm-s-3024-night .CodeMirror-gutters { background: #090300; border-right: 0px; }
.cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20; }
.cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855; }
.cm-s-3024-night .CodeMirror-linenumber { color: #5c5855; }
.cm-s-3024-night .CodeMirror-cursor { border-left: 1px solid #807d7c; }
.cm-s-3024-night span.cm-comment { color: #cdab53; }
.cm-s-3024-night span.cm-atom { color: #a16a94; }
.cm-s-3024-night span.cm-number { color: #a16a94; }
.cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute { color: #01a252; }
.cm-s-3024-night span.cm-keyword { color: #db2d20; }
.cm-s-3024-night span.cm-string { color: #fded02; }
.cm-s-3024-night span.cm-variable { color: #01a252; }
.cm-s-3024-night span.cm-variable-2 { color: #01a0e4; }
.cm-s-3024-night span.cm-def { color: #e8bbd0; }
.cm-s-3024-night span.cm-bracket { color: #d6d5d4; }
.cm-s-3024-night span.cm-tag { color: #db2d20; }
.cm-s-3024-night span.cm-link { color: #a16a94; }
.cm-s-3024-night span.cm-error { background: #db2d20; color: #807d7c; }
.cm-s-3024-night .CodeMirror-activeline-background { background: #2F2F2F; }
.cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }
+32
View File
@@ -0,0 +1,32 @@
.cm-s-abcdef.CodeMirror { background: #0f0f0f; color: #defdef; }
.cm-s-abcdef div.CodeMirror-selected { background: #515151; }
.cm-s-abcdef .CodeMirror-line::selection, .cm-s-abcdef .CodeMirror-line > span::selection, .cm-s-abcdef .CodeMirror-line > span > span::selection { background: rgba(56, 56, 56, 0.99); }
.cm-s-abcdef .CodeMirror-line::-moz-selection, .cm-s-abcdef .CodeMirror-line > span::-moz-selection, .cm-s-abcdef .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 56, 56, 0.99); }
.cm-s-abcdef .CodeMirror-gutters { background: #555; border-right: 2px solid #314151; }
.cm-s-abcdef .CodeMirror-guttermarker { color: #222; }
.cm-s-abcdef .CodeMirror-guttermarker-subtle { color: azure; }
.cm-s-abcdef .CodeMirror-linenumber { color: #FFFFFF; }
.cm-s-abcdef .CodeMirror-cursor { border-left: 1px solid #00FF00; }
.cm-s-abcdef span.cm-keyword { color: darkgoldenrod; font-weight: bold; }
.cm-s-abcdef span.cm-atom { color: #77F; }
.cm-s-abcdef span.cm-number { color: violet; }
.cm-s-abcdef span.cm-def { color: #fffabc; }
.cm-s-abcdef span.cm-variable { color: #abcdef; }
.cm-s-abcdef span.cm-variable-2 { color: #cacbcc; }
.cm-s-abcdef span.cm-variable-3, .cm-s-abcdef span.cm-type { color: #def; }
.cm-s-abcdef span.cm-property { color: #fedcba; }
.cm-s-abcdef span.cm-operator { color: #ff0; }
.cm-s-abcdef span.cm-comment { color: #7a7b7c; font-style: italic;}
.cm-s-abcdef span.cm-string { color: #2b4; }
.cm-s-abcdef span.cm-meta { color: #C9F; }
.cm-s-abcdef span.cm-qualifier { color: #FFF700; }
.cm-s-abcdef span.cm-builtin { color: #30aabc; }
.cm-s-abcdef span.cm-bracket { color: #8a8a8a; }
.cm-s-abcdef span.cm-tag { color: #FFDD44; }
.cm-s-abcdef span.cm-attribute { color: #DDFF00; }
.cm-s-abcdef span.cm-error { color: #FF0000; }
.cm-s-abcdef span.cm-header { color: aquamarine; font-weight: bold; }
.cm-s-abcdef span.cm-link { color: blueviolet; }
.cm-s-abcdef .CodeMirror-activeline-background { background: #314151; }
@@ -0,0 +1,5 @@
.cm-s-ambiance.CodeMirror {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
File diff suppressed because one or more lines are too long
+42
View File
@@ -0,0 +1,42 @@
/* Based on https://github.com/dempfi/ayu */
.cm-s-ayu-dark.CodeMirror { background: #0a0e14; color: #b3b1ad; }
.cm-s-ayu-dark div.CodeMirror-selected { background: #273747; }
.cm-s-ayu-dark .CodeMirror-line::selection, .cm-s-ayu-dark .CodeMirror-line > span::selection, .cm-s-ayu-dark .CodeMirror-line > span > span::selection { background: rgba(39, 55, 71, 99); }
.cm-s-ayu-dark .CodeMirror-line::-moz-selection, .cm-s-ayu-dark .CodeMirror-line > span::-moz-selection, .cm-s-ayu-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 55, 71, 99); }
.cm-s-ayu-dark .CodeMirror-gutters { background: #0a0e14; border-right: 0px; }
.cm-s-ayu-dark .CodeMirror-guttermarker { color: white; }
.cm-s-ayu-dark .CodeMirror-guttermarker-subtle { color: #3d424d; }
.cm-s-ayu-dark .CodeMirror-linenumber { color: #3d424d; }
.cm-s-ayu-dark .CodeMirror-cursor { border-left: 1px solid #e6b450; }
.cm-s-ayu-dark span.cm-comment { color: #626a73; }
.cm-s-ayu-dark span.cm-atom { color: #ae81ff; }
.cm-s-ayu-dark span.cm-number { color: #e6b450; }
.cm-s-ayu-dark span.cm-comment.cm-attribute { color: #ffb454; }
.cm-s-ayu-dark span.cm-comment.cm-def { color: rgba(57, 186, 230, 80); }
.cm-s-ayu-dark span.cm-comment.cm-tag { color: #39bae6; }
.cm-s-ayu-dark span.cm-comment.cm-type { color: #5998a6; }
.cm-s-ayu-dark span.cm-property, .cm-s-ayu-dark span.cm-attribute { color: #ffb454; }
.cm-s-ayu-dark span.cm-keyword { color: #ff8f40; }
.cm-s-ayu-dark span.cm-builtin { color: #e6b450; }
.cm-s-ayu-dark span.cm-string { color: #c2d94c; }
.cm-s-ayu-dark span.cm-variable { color: #b3b1ad; }
.cm-s-ayu-dark span.cm-variable-2 { color: #f07178; }
.cm-s-ayu-dark span.cm-variable-3 { color: #39bae6; }
.cm-s-ayu-dark span.cm-type { color: #ff8f40; }
.cm-s-ayu-dark span.cm-def { color: #ffee99; }
.cm-s-ayu-dark span.cm-bracket { color: #f8f8f2; }
.cm-s-ayu-dark span.cm-tag { color: rgba(57, 186, 230, 80); }
.cm-s-ayu-dark span.cm-header { color: #c2d94c; }
.cm-s-ayu-dark span.cm-link { color: #39bae6; }
.cm-s-ayu-dark span.cm-error { color: #ff3333; }
.cm-s-ayu-dark .CodeMirror-activeline-background { background: #01060e; }
.cm-s-ayu-dark .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
+43
View File
@@ -0,0 +1,43 @@
/* Based on https://github.com/dempfi/ayu */
.cm-s-ayu-mirage.CodeMirror { background: #1f2430; color: #cbccc6; }
.cm-s-ayu-mirage div.CodeMirror-selected { background: #34455a; }
.cm-s-ayu-mirage .CodeMirror-line::selection, .cm-s-ayu-mirage .CodeMirror-line > span::selection, .cm-s-ayu-mirage .CodeMirror-line > span > span::selection { background: #34455a; }
.cm-s-ayu-mirage .CodeMirror-line::-moz-selection, .cm-s-ayu-mirage .CodeMirror-line > span::-moz-selection, .cm-s-ayu-mirage .CodeMirror-line > span > span::-moz-selection { background: rgba(25, 30, 42, 99); }
.cm-s-ayu-mirage .CodeMirror-gutters { background: #1f2430; border-right: 0px; }
.cm-s-ayu-mirage .CodeMirror-guttermarker { color: white; }
.cm-s-ayu-mirage .CodeMirror-guttermarker-subtle { color: rgba(112, 122, 140, 66); }
.cm-s-ayu-mirage .CodeMirror-linenumber { color: rgba(61, 66, 77, 99); }
.cm-s-ayu-mirage .CodeMirror-cursor { border-left: 1px solid #ffcc66; }
.cm-s-ayu-mirage span.cm-comment { color: #5c6773; font-style:italic; }
.cm-s-ayu-mirage span.cm-atom { color: #ae81ff; }
.cm-s-ayu-mirage span.cm-number { color: #ffcc66; }
.cm-s-ayu-mirage span.cm-comment.cm-attribute { color: #ffd580; }
.cm-s-ayu-mirage span.cm-comment.cm-def { color: #d4bfff; }
.cm-s-ayu-mirage span.cm-comment.cm-tag { color: #5ccfe6; }
.cm-s-ayu-mirage span.cm-comment.cm-type { color: #5998a6; }
.cm-s-ayu-mirage span.cm-property { color: #f29e74; }
.cm-s-ayu-mirage span.cm-attribute { color: #ffd580; }
.cm-s-ayu-mirage span.cm-keyword { color: #ffa759; }
.cm-s-ayu-mirage span.cm-builtin { color: #ffcc66; }
.cm-s-ayu-mirage span.cm-string { color: #bae67e; }
.cm-s-ayu-mirage span.cm-variable { color: #cbccc6; }
.cm-s-ayu-mirage span.cm-variable-2 { color: #f28779; }
.cm-s-ayu-mirage span.cm-variable-3 { color: #5ccfe6; }
.cm-s-ayu-mirage span.cm-type { color: #ffa759; }
.cm-s-ayu-mirage span.cm-def { color: #ffd580; }
.cm-s-ayu-mirage span.cm-bracket { color: rgba(92, 207, 230, 80); }
.cm-s-ayu-mirage span.cm-tag { color: #5ccfe6; }
.cm-s-ayu-mirage span.cm-header { color: #bae67e; }
.cm-s-ayu-mirage span.cm-link { color: #5ccfe6; }
.cm-s-ayu-mirage span.cm-error { color: #ff3333; }
.cm-s-ayu-mirage .CodeMirror-activeline-background { background: #191e2a; }
.cm-s-ayu-mirage .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
+38
View File
@@ -0,0 +1,38 @@
/*
Name: Base16 Default Dark
Author: Chris Kempson (http://chriskempson.com)
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
*/
.cm-s-base16-dark.CodeMirror { background: #151515; color: #e0e0e0; }
.cm-s-base16-dark div.CodeMirror-selected { background: #303030; }
.cm-s-base16-dark .CodeMirror-line::selection, .cm-s-base16-dark .CodeMirror-line > span::selection, .cm-s-base16-dark .CodeMirror-line > span > span::selection { background: rgba(48, 48, 48, .99); }
.cm-s-base16-dark .CodeMirror-line::-moz-selection, .cm-s-base16-dark .CodeMirror-line > span::-moz-selection, .cm-s-base16-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(48, 48, 48, .99); }
.cm-s-base16-dark .CodeMirror-gutters { background: #151515; border-right: 0px; }
.cm-s-base16-dark .CodeMirror-guttermarker { color: #ac4142; }
.cm-s-base16-dark .CodeMirror-guttermarker-subtle { color: #505050; }
.cm-s-base16-dark .CodeMirror-linenumber { color: #505050; }
.cm-s-base16-dark .CodeMirror-cursor { border-left: 1px solid #b0b0b0; }
.cm-s-base16-dark span.cm-comment { color: #8f5536; }
.cm-s-base16-dark span.cm-atom { color: #aa759f; }
.cm-s-base16-dark span.cm-number { color: #aa759f; }
.cm-s-base16-dark span.cm-property, .cm-s-base16-dark span.cm-attribute { color: #90a959; }
.cm-s-base16-dark span.cm-keyword { color: #ac4142; }
.cm-s-base16-dark span.cm-string { color: #f4bf75; }
.cm-s-base16-dark span.cm-variable { color: #90a959; }
.cm-s-base16-dark span.cm-variable-2 { color: #6a9fb5; }
.cm-s-base16-dark span.cm-def { color: #d28445; }
.cm-s-base16-dark span.cm-bracket { color: #e0e0e0; }
.cm-s-base16-dark span.cm-tag { color: #ac4142; }
.cm-s-base16-dark span.cm-link { color: #aa759f; }
.cm-s-base16-dark span.cm-error { background: #ac4142; color: #b0b0b0; }
.cm-s-base16-dark .CodeMirror-activeline-background { background: #202020; }
.cm-s-base16-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }
+38
View File
@@ -0,0 +1,38 @@
/*
Name: Base16 Default Light
Author: Chris Kempson (http://chriskempson.com)
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
*/
.cm-s-base16-light.CodeMirror { background: #f5f5f5; color: #202020; }
.cm-s-base16-light div.CodeMirror-selected { background: #e0e0e0; }
.cm-s-base16-light .CodeMirror-line::selection, .cm-s-base16-light .CodeMirror-line > span::selection, .cm-s-base16-light .CodeMirror-line > span > span::selection { background: #e0e0e0; }
.cm-s-base16-light .CodeMirror-line::-moz-selection, .cm-s-base16-light .CodeMirror-line > span::-moz-selection, .cm-s-base16-light .CodeMirror-line > span > span::-moz-selection { background: #e0e0e0; }
.cm-s-base16-light .CodeMirror-gutters { background: #f5f5f5; border-right: 0px; }
.cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142; }
.cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0; }
.cm-s-base16-light .CodeMirror-linenumber { color: #b0b0b0; }
.cm-s-base16-light .CodeMirror-cursor { border-left: 1px solid #505050; }
.cm-s-base16-light span.cm-comment { color: #8f5536; }
.cm-s-base16-light span.cm-atom { color: #aa759f; }
.cm-s-base16-light span.cm-number { color: #aa759f; }
.cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute { color: #90a959; }
.cm-s-base16-light span.cm-keyword { color: #ac4142; }
.cm-s-base16-light span.cm-string { color: #f4bf75; }
.cm-s-base16-light span.cm-variable { color: #90a959; }
.cm-s-base16-light span.cm-variable-2 { color: #6a9fb5; }
.cm-s-base16-light span.cm-def { color: #d28445; }
.cm-s-base16-light span.cm-bracket { color: #202020; }
.cm-s-base16-light span.cm-tag { color: #ac4142; }
.cm-s-base16-light span.cm-link { color: #aa759f; }
.cm-s-base16-light span.cm-error { background: #ac4142; color: #505050; }
.cm-s-base16-light .CodeMirror-activeline-background { background: #DDDCDC; }
.cm-s-base16-light .CodeMirror-matchingbracket { color: #f5f5f5 !important; background-color: #6A9FB5 !important}
+34
View File
@@ -0,0 +1,34 @@
/*
Name: Bespin
Author: Mozilla / Jan T. Sott
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
*/
.cm-s-bespin.CodeMirror {background: #28211c; color: #9d9b97;}
.cm-s-bespin div.CodeMirror-selected {background: #36312e !important;}
.cm-s-bespin .CodeMirror-gutters {background: #28211c; border-right: 0px;}
.cm-s-bespin .CodeMirror-linenumber {color: #666666;}
.cm-s-bespin .CodeMirror-cursor {border-left: 1px solid #797977 !important;}
.cm-s-bespin span.cm-comment {color: #937121;}
.cm-s-bespin span.cm-atom {color: #9b859d;}
.cm-s-bespin span.cm-number {color: #9b859d;}
.cm-s-bespin span.cm-property, .cm-s-bespin span.cm-attribute {color: #54be0d;}
.cm-s-bespin span.cm-keyword {color: #cf6a4c;}
.cm-s-bespin span.cm-string {color: #f9ee98;}
.cm-s-bespin span.cm-variable {color: #54be0d;}
.cm-s-bespin span.cm-variable-2 {color: #5ea6ea;}
.cm-s-bespin span.cm-def {color: #cf7d34;}
.cm-s-bespin span.cm-error {background: #cf6a4c; color: #797977;}
.cm-s-bespin span.cm-bracket {color: #9d9b97;}
.cm-s-bespin span.cm-tag {color: #cf6a4c;}
.cm-s-bespin span.cm-link {color: #9b859d;}
.cm-s-bespin .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;}
.cm-s-bespin .CodeMirror-activeline-background { background: #404040; }
+32
View File
@@ -0,0 +1,32 @@
/* Port of TextMate's Blackboard theme */
.cm-s-blackboard.CodeMirror { background: #0C1021; color: #F8F8F8; }
.cm-s-blackboard div.CodeMirror-selected { background: #253B76; }
.cm-s-blackboard .CodeMirror-line::selection, .cm-s-blackboard .CodeMirror-line > span::selection, .cm-s-blackboard .CodeMirror-line > span > span::selection { background: rgba(37, 59, 118, .99); }
.cm-s-blackboard .CodeMirror-line::-moz-selection, .cm-s-blackboard .CodeMirror-line > span::-moz-selection, .cm-s-blackboard .CodeMirror-line > span > span::-moz-selection { background: rgba(37, 59, 118, .99); }
.cm-s-blackboard .CodeMirror-gutters { background: #0C1021; border-right: 0; }
.cm-s-blackboard .CodeMirror-guttermarker { color: #FBDE2D; }
.cm-s-blackboard .CodeMirror-guttermarker-subtle { color: #888; }
.cm-s-blackboard .CodeMirror-linenumber { color: #888; }
.cm-s-blackboard .CodeMirror-cursor { border-left: 1px solid #A7A7A7; }
.cm-s-blackboard .cm-keyword { color: #FBDE2D; }
.cm-s-blackboard .cm-atom { color: #D8FA3C; }
.cm-s-blackboard .cm-number { color: #D8FA3C; }
.cm-s-blackboard .cm-def { color: #8DA6CE; }
.cm-s-blackboard .cm-variable { color: #FF6400; }
.cm-s-blackboard .cm-operator { color: #FBDE2D; }
.cm-s-blackboard .cm-comment { color: #AEAEAE; }
.cm-s-blackboard .cm-string { color: #61CE3C; }
.cm-s-blackboard .cm-string-2 { color: #61CE3C; }
.cm-s-blackboard .cm-meta { color: #D8FA3C; }
.cm-s-blackboard .cm-builtin { color: #8DA6CE; }
.cm-s-blackboard .cm-tag { color: #8DA6CE; }
.cm-s-blackboard .cm-attribute { color: #8DA6CE; }
.cm-s-blackboard .cm-header { color: #FF6400; }
.cm-s-blackboard .cm-hr { color: #AEAEAE; }
.cm-s-blackboard .cm-link { color: #8DA6CE; }
.cm-s-blackboard .cm-error { background: #9D1E15; color: #F8F8F8; }
.cm-s-blackboard .CodeMirror-activeline-background { background: #3C3636; }
.cm-s-blackboard .CodeMirror-matchingbracket { outline:1px solid grey;color:white !important; }
+25
View File
@@ -0,0 +1,25 @@
.cm-s-cobalt.CodeMirror { background: #002240; color: white; }
.cm-s-cobalt div.CodeMirror-selected { background: #b36539; }
.cm-s-cobalt .CodeMirror-line::selection, .cm-s-cobalt .CodeMirror-line > span::selection, .cm-s-cobalt .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99); }
.cm-s-cobalt .CodeMirror-line::-moz-selection, .cm-s-cobalt .CodeMirror-line > span::-moz-selection, .cm-s-cobalt .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99); }
.cm-s-cobalt .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; }
.cm-s-cobalt .CodeMirror-guttermarker { color: #ffee80; }
.cm-s-cobalt .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
.cm-s-cobalt .CodeMirror-linenumber { color: #d0d0d0; }
.cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white; }
.cm-s-cobalt span.cm-comment { color: #08f; }
.cm-s-cobalt span.cm-atom { color: #845dc4; }
.cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; }
.cm-s-cobalt span.cm-keyword { color: #ffee80; }
.cm-s-cobalt span.cm-string { color: #3ad900; }
.cm-s-cobalt span.cm-meta { color: #ff9d00; }
.cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; }
.cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def, .cm-s-cobalt .cm-type { color: white; }
.cm-s-cobalt span.cm-bracket { color: #d8d8d8; }
.cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; }
.cm-s-cobalt span.cm-link { color: #845dc4; }
.cm-s-cobalt span.cm-error { color: #9d1e15; }
.cm-s-cobalt .CodeMirror-activeline-background { background: #002D57; }
.cm-s-cobalt .CodeMirror-matchingbracket { outline:1px solid grey;color:white !important; }
+33
View File
@@ -0,0 +1,33 @@
.cm-s-colorforth.CodeMirror { background: #000000; color: #f8f8f8; }
.cm-s-colorforth .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; }
.cm-s-colorforth .CodeMirror-guttermarker { color: #FFBD40; }
.cm-s-colorforth .CodeMirror-guttermarker-subtle { color: #78846f; }
.cm-s-colorforth .CodeMirror-linenumber { color: #bababa; }
.cm-s-colorforth .CodeMirror-cursor { border-left: 1px solid white; }
.cm-s-colorforth span.cm-comment { color: #ededed; }
.cm-s-colorforth span.cm-def { color: #ff1c1c; font-weight:bold; }
.cm-s-colorforth span.cm-keyword { color: #ffd900; }
.cm-s-colorforth span.cm-builtin { color: #00d95a; }
.cm-s-colorforth span.cm-variable { color: #73ff00; }
.cm-s-colorforth span.cm-string { color: #007bff; }
.cm-s-colorforth span.cm-number { color: #00c4ff; }
.cm-s-colorforth span.cm-atom { color: #606060; }
.cm-s-colorforth span.cm-variable-2 { color: #EEE; }
.cm-s-colorforth span.cm-variable-3, .cm-s-colorforth span.cm-type { color: #DDD; }
.cm-s-colorforth span.cm-property {}
.cm-s-colorforth span.cm-operator {}
.cm-s-colorforth span.cm-meta { color: yellow; }
.cm-s-colorforth span.cm-qualifier { color: #FFF700; }
.cm-s-colorforth span.cm-bracket { color: #cc7; }
.cm-s-colorforth span.cm-tag { color: #FFBD40; }
.cm-s-colorforth span.cm-attribute { color: #FFF700; }
.cm-s-colorforth span.cm-error { color: #f00; }
.cm-s-colorforth div.CodeMirror-selected { background: #333d53; }
.cm-s-colorforth span.cm-compilation { background: rgba(255, 255, 255, 0.12); }
.cm-s-colorforth .CodeMirror-activeline-background { background: #253540; }
+53
View File
@@ -0,0 +1,53 @@
/**
Name: IntelliJ IDEA darcula theme
From IntelliJ IDEA by JetBrains
*/
.cm-s-darcula { font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif;}
.cm-s-darcula.CodeMirror { background: #2B2B2B; color: #A9B7C6; }
.cm-s-darcula span.cm-meta { color: #BBB529; }
.cm-s-darcula span.cm-number { color: #6897BB; }
.cm-s-darcula span.cm-keyword { color: #CC7832; line-height: 1em; font-weight: bold; }
.cm-s-darcula span.cm-def { color: #A9B7C6; font-style: italic; }
.cm-s-darcula span.cm-variable { color: #A9B7C6; }
.cm-s-darcula span.cm-variable-2 { color: #A9B7C6; }
.cm-s-darcula span.cm-variable-3 { color: #9876AA; }
.cm-s-darcula span.cm-type { color: #AABBCC; font-weight: bold; }
.cm-s-darcula span.cm-property { color: #FFC66D; }
.cm-s-darcula span.cm-operator { color: #A9B7C6; }
.cm-s-darcula span.cm-string { color: #6A8759; }
.cm-s-darcula span.cm-string-2 { color: #6A8759; }
.cm-s-darcula span.cm-comment { color: #61A151; font-style: italic; }
.cm-s-darcula span.cm-link { color: #CC7832; }
.cm-s-darcula span.cm-atom { color: #CC7832; }
.cm-s-darcula span.cm-error { color: #BC3F3C; }
.cm-s-darcula span.cm-tag { color: #629755; font-weight: bold; font-style: italic; text-decoration: underline; }
.cm-s-darcula span.cm-attribute { color: #6897bb; }
.cm-s-darcula span.cm-qualifier { color: #6A8759; }
.cm-s-darcula span.cm-bracket { color: #A9B7C6; }
.cm-s-darcula span.cm-builtin { color: #FF9E59; }
.cm-s-darcula span.cm-special { color: #FF9E59; }
.cm-s-darcula span.cm-matchhighlight { color: #FFFFFF; background-color: rgba(50, 89, 48, .7); font-weight: normal;}
.cm-s-darcula span.cm-searching { color: #FFFFFF; background-color: rgba(61, 115, 59, .7); font-weight: normal;}
.cm-s-darcula .CodeMirror-cursor { border-left: 1px solid #A9B7C6; }
.cm-s-darcula .CodeMirror-activeline-background { background: #323232; }
.cm-s-darcula .CodeMirror-gutters { background: #313335; border-right: 1px solid #313335; }
.cm-s-darcula .CodeMirror-guttermarker { color: #FFEE80; }
.cm-s-darcula .CodeMirror-guttermarker-subtle { color: #D0D0D0; }
.cm-s-darcula .CodeMirrir-linenumber { color: #606366; }
.cm-s-darcula .CodeMirror-matchingbracket { background-color: #3B514D; color: #FFEF28 !important; font-weight: bold; }
.cm-s-darcula div.CodeMirror-selected { background: #214283; }
.CodeMirror-hints.darcula {
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
color: #9C9E9E;
background-color: #3B3E3F !important;
}
.CodeMirror-hints.darcula .CodeMirror-hint-active {
background-color: #494D4E !important;
color: #9C9E9E !important;
}
+40
View File
@@ -0,0 +1,40 @@
/*
Name: dracula
Author: Michael Kaminsky (http://github.com/mkaminsky11)
Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)
*/
.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {
background-color: #282a36 !important;
color: #f8f8f2 !important;
border: none;
}
.cm-s-dracula .CodeMirror-gutters { color: #282a36; }
.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }
.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }
.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }
.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }
.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }
.cm-s-dracula span.cm-comment { color: #6272a4; }
.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }
.cm-s-dracula span.cm-number { color: #bd93f9; }
.cm-s-dracula span.cm-variable { color: #50fa7b; }
.cm-s-dracula span.cm-variable-2 { color: white; }
.cm-s-dracula span.cm-def { color: #50fa7b; }
.cm-s-dracula span.cm-operator { color: #ff79c6; }
.cm-s-dracula span.cm-keyword { color: #ff79c6; }
.cm-s-dracula span.cm-atom { color: #bd93f9; }
.cm-s-dracula span.cm-meta { color: #f8f8f2; }
.cm-s-dracula span.cm-tag { color: #ff79c6; }
.cm-s-dracula span.cm-attribute { color: #50fa7b; }
.cm-s-dracula span.cm-qualifier { color: #50fa7b; }
.cm-s-dracula span.cm-property { color: #66d9ef; }
.cm-s-dracula span.cm-builtin { color: #50fa7b; }
.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }
.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }
.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }
+35
View File
@@ -0,0 +1,35 @@
/*
Name: DuoTone-Dark
Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/)
*/
.cm-s-duotone-dark.CodeMirror { background: #2a2734; color: #6c6783; }
.cm-s-duotone-dark div.CodeMirror-selected { background: #545167!important; }
.cm-s-duotone-dark .CodeMirror-gutters { background: #2a2734; border-right: 0px; }
.cm-s-duotone-dark .CodeMirror-linenumber { color: #545167; }
/* begin cursor */
.cm-s-duotone-dark .CodeMirror-cursor { border-left: 1px solid #ffad5c; /* border-left: 1px solid #ffad5c80; */ border-right: .5em solid #ffad5c; /* border-right: .5em solid #ffad5c80; */ opacity: .5; }
.cm-s-duotone-dark .CodeMirror-activeline-background { background: #363342; /* background: #36334280; */ opacity: .5;}
.cm-s-duotone-dark .cm-fat-cursor .CodeMirror-cursor { background: #ffad5c; /* background: #ffad5c80; */ opacity: .5;}
/* end cursor */
.cm-s-duotone-dark span.cm-atom, .cm-s-duotone-dark span.cm-number, .cm-s-duotone-dark span.cm-keyword, .cm-s-duotone-dark span.cm-variable, .cm-s-duotone-dark span.cm-attribute, .cm-s-duotone-dark span.cm-quote, .cm-s-duotone-dark span.cm-hr, .cm-s-duotone-dark span.cm-link { color: #ffcc99; }
.cm-s-duotone-dark span.cm-property { color: #9a86fd; }
.cm-s-duotone-dark span.cm-punctuation, .cm-s-duotone-dark span.cm-unit, .cm-s-duotone-dark span.cm-negative { color: #e09142; }
.cm-s-duotone-dark span.cm-string { color: #ffb870; }
.cm-s-duotone-dark span.cm-operator { color: #ffad5c; }
.cm-s-duotone-dark span.cm-positive { color: #6a51e6; }
.cm-s-duotone-dark span.cm-variable-2, .cm-s-duotone-dark span.cm-variable-3, .cm-s-duotone-dark span.cm-type, .cm-s-duotone-dark span.cm-string-2, .cm-s-duotone-dark span.cm-url { color: #7a63ee; }
.cm-s-duotone-dark span.cm-def, .cm-s-duotone-dark span.cm-tag, .cm-s-duotone-dark span.cm-builtin, .cm-s-duotone-dark span.cm-qualifier, .cm-s-duotone-dark span.cm-header, .cm-s-duotone-dark span.cm-em { color: #eeebff; }
.cm-s-duotone-dark span.cm-bracket, .cm-s-duotone-dark span.cm-comment { color: #6c6783; }
/* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */
.cm-s-duotone-dark span.cm-error, .cm-s-duotone-dark span.cm-invalidchar { color: #f00; }
.cm-s-duotone-dark span.cm-header { font-weight: normal; }
.cm-s-duotone-dark .CodeMirror-matchingbracket { text-decoration: underline; color: #eeebff !important; }
+36
View File
@@ -0,0 +1,36 @@
/*
Name: DuoTone-Light
Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/)
*/
.cm-s-duotone-light.CodeMirror { background: #faf8f5; color: #b29762; }
.cm-s-duotone-light div.CodeMirror-selected { background: #e3dcce !important; }
.cm-s-duotone-light .CodeMirror-gutters { background: #faf8f5; border-right: 0px; }
.cm-s-duotone-light .CodeMirror-linenumber { color: #cdc4b1; }
/* begin cursor */
.cm-s-duotone-light .CodeMirror-cursor { border-left: 1px solid #93abdc; /* border-left: 1px solid #93abdc80; */ border-right: .5em solid #93abdc; /* border-right: .5em solid #93abdc80; */ opacity: .5; }
.cm-s-duotone-light .CodeMirror-activeline-background { background: #e3dcce; /* background: #e3dcce80; */ opacity: .5; }
.cm-s-duotone-light .cm-fat-cursor .CodeMirror-cursor { background: #93abdc; /* #93abdc80; */ opacity: .5; }
/* end cursor */
.cm-s-duotone-light span.cm-atom, .cm-s-duotone-light span.cm-number, .cm-s-duotone-light span.cm-keyword, .cm-s-duotone-light span.cm-variable, .cm-s-duotone-light span.cm-attribute, .cm-s-duotone-light span.cm-quote, .cm-s-duotone-light-light span.cm-hr, .cm-s-duotone-light-light span.cm-link { color: #063289; }
.cm-s-duotone-light span.cm-property { color: #b29762; }
.cm-s-duotone-light span.cm-punctuation, .cm-s-duotone-light span.cm-unit, .cm-s-duotone-light span.cm-negative { color: #063289; }
.cm-s-duotone-light span.cm-string, .cm-s-duotone-light span.cm-operator { color: #1659df; }
.cm-s-duotone-light span.cm-positive { color: #896724; }
.cm-s-duotone-light span.cm-variable-2, .cm-s-duotone-light span.cm-variable-3, .cm-s-duotone-light span.cm-type, .cm-s-duotone-light span.cm-string-2, .cm-s-duotone-light span.cm-url { color: #896724; }
.cm-s-duotone-light span.cm-def, .cm-s-duotone-light span.cm-tag, .cm-s-duotone-light span.cm-builtin, .cm-s-duotone-light span.cm-qualifier, .cm-s-duotone-light span.cm-header, .cm-s-duotone-light span.cm-em { color: #2d2006; }
.cm-s-duotone-light span.cm-bracket, .cm-s-duotone-light span.cm-comment { color: #b6ad9a; }
/* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */
/* .cm-s-duotone-light span.cm-error { background: #896724; color: #728fcb; } */
.cm-s-duotone-light span.cm-error, .cm-s-duotone-light span.cm-invalidchar { color: #f00; }
.cm-s-duotone-light span.cm-header { font-weight: normal; }
.cm-s-duotone-light .CodeMirror-matchingbracket { text-decoration: underline; color: #faf8f5 !important; }
+23
View File
@@ -0,0 +1,23 @@
.cm-s-eclipse span.cm-meta { color: #FF1717; }
.cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; }
.cm-s-eclipse span.cm-atom { color: #219; }
.cm-s-eclipse span.cm-number { color: #164; }
.cm-s-eclipse span.cm-def { color: #00f; }
.cm-s-eclipse span.cm-variable { color: black; }
.cm-s-eclipse span.cm-variable-2 { color: #0000C0; }
.cm-s-eclipse span.cm-variable-3, .cm-s-eclipse span.cm-type { color: #0000C0; }
.cm-s-eclipse span.cm-property { color: black; }
.cm-s-eclipse span.cm-operator { color: black; }
.cm-s-eclipse span.cm-comment { color: #3F7F5F; }
.cm-s-eclipse span.cm-string { color: #2A00FF; }
.cm-s-eclipse span.cm-string-2 { color: #f50; }
.cm-s-eclipse span.cm-qualifier { color: #555; }
.cm-s-eclipse span.cm-builtin { color: #30a; }
.cm-s-eclipse span.cm-bracket { color: #cc7; }
.cm-s-eclipse span.cm-tag { color: #170; }
.cm-s-eclipse span.cm-attribute { color: #00c; }
.cm-s-eclipse span.cm-link { color: #219; }
.cm-s-eclipse span.cm-error { color: #f00; }
.cm-s-eclipse .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-eclipse .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
+13
View File
@@ -0,0 +1,13 @@
.cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom { color: #762; }
.cm-s-elegant span.cm-comment { color: #262; font-style: italic; line-height: 1em; }
.cm-s-elegant span.cm-meta { color: #555; font-style: italic; line-height: 1em; }
.cm-s-elegant span.cm-variable { color: black; }
.cm-s-elegant span.cm-variable-2 { color: #b11; }
.cm-s-elegant span.cm-qualifier { color: #555; }
.cm-s-elegant span.cm-keyword { color: #730; }
.cm-s-elegant span.cm-builtin { color: #30a; }
.cm-s-elegant span.cm-link { color: #762; }
.cm-s-elegant span.cm-error { background-color: #fdd; }
.cm-s-elegant .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-elegant .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
+34
View File
@@ -0,0 +1,34 @@
.cm-s-erlang-dark.CodeMirror { background: #002240; color: white; }
.cm-s-erlang-dark div.CodeMirror-selected { background: #b36539; }
.cm-s-erlang-dark .CodeMirror-line::selection, .cm-s-erlang-dark .CodeMirror-line > span::selection, .cm-s-erlang-dark .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99); }
.cm-s-erlang-dark .CodeMirror-line::-moz-selection, .cm-s-erlang-dark .CodeMirror-line > span::-moz-selection, .cm-s-erlang-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99); }
.cm-s-erlang-dark .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; }
.cm-s-erlang-dark .CodeMirror-guttermarker { color: white; }
.cm-s-erlang-dark .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
.cm-s-erlang-dark .CodeMirror-linenumber { color: #d0d0d0; }
.cm-s-erlang-dark .CodeMirror-cursor { border-left: 1px solid white; }
.cm-s-erlang-dark span.cm-quote { color: #ccc; }
.cm-s-erlang-dark span.cm-atom { color: #f133f1; }
.cm-s-erlang-dark span.cm-attribute { color: #ff80e1; }
.cm-s-erlang-dark span.cm-bracket { color: #ff9d00; }
.cm-s-erlang-dark span.cm-builtin { color: #eaa; }
.cm-s-erlang-dark span.cm-comment { color: #77f; }
.cm-s-erlang-dark span.cm-def { color: #e7a; }
.cm-s-erlang-dark span.cm-keyword { color: #ffee80; }
.cm-s-erlang-dark span.cm-meta { color: #50fefe; }
.cm-s-erlang-dark span.cm-number { color: #ffd0d0; }
.cm-s-erlang-dark span.cm-operator { color: #d55; }
.cm-s-erlang-dark span.cm-property { color: #ccc; }
.cm-s-erlang-dark span.cm-qualifier { color: #ccc; }
.cm-s-erlang-dark span.cm-special { color: #ffbbbb; }
.cm-s-erlang-dark span.cm-string { color: #3ad900; }
.cm-s-erlang-dark span.cm-string-2 { color: #ccc; }
.cm-s-erlang-dark span.cm-tag { color: #9effff; }
.cm-s-erlang-dark span.cm-variable { color: #50fe50; }
.cm-s-erlang-dark span.cm-variable-2 { color: #e0e; }
.cm-s-erlang-dark span.cm-variable-3, .cm-s-erlang-dark span.cm-type { color: #ccc; }
.cm-s-erlang-dark span.cm-error { color: #9d1e15; }
.cm-s-erlang-dark .CodeMirror-activeline-background { background: #013461; }
.cm-s-erlang-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }
+37
View File
@@ -0,0 +1,37 @@
/*
Name: gruvbox-dark
Author: kRkk (https://github.com/krkk)
Original gruvbox color scheme by Pavel Pertsev (https://github.com/morhetz/gruvbox)
*/
.cm-s-gruvbox-dark.CodeMirror, .cm-s-gruvbox-dark .CodeMirror-gutters { background-color: #282828; color: #bdae93; }
.cm-s-gruvbox-dark .CodeMirror-gutters {background: #282828; border-right: 0px;}
.cm-s-gruvbox-dark .CodeMirror-linenumber {color: #7c6f64;}
.cm-s-gruvbox-dark .CodeMirror-cursor { border-left: 1px solid #ebdbb2; }
.cm-s-gruvbox-dark div.CodeMirror-selected { background: #928374; }
.cm-s-gruvbox-dark span.cm-meta { color: #83a598; }
.cm-s-gruvbox-dark span.cm-comment { color: #928374; }
.cm-s-gruvbox-dark span.cm-number, span.cm-atom { color: #d3869b; }
.cm-s-gruvbox-dark span.cm-keyword { color: #f84934; }
.cm-s-gruvbox-dark span.cm-variable { color: #ebdbb2; }
.cm-s-gruvbox-dark span.cm-variable-2 { color: #ebdbb2; }
.cm-s-gruvbox-dark span.cm-variable-3, .cm-s-gruvbox-dark span.cm-type { color: #fabd2f; }
.cm-s-gruvbox-dark span.cm-operator { color: #ebdbb2; }
.cm-s-gruvbox-dark span.cm-callee { color: #ebdbb2; }
.cm-s-gruvbox-dark span.cm-def { color: #ebdbb2; }
.cm-s-gruvbox-dark span.cm-property { color: #ebdbb2; }
.cm-s-gruvbox-dark span.cm-string { color: #b8bb26; }
.cm-s-gruvbox-dark span.cm-string-2 { color: #8ec07c; }
.cm-s-gruvbox-dark span.cm-qualifier { color: #8ec07c; }
.cm-s-gruvbox-dark span.cm-attribute { color: #8ec07c; }
.cm-s-gruvbox-dark .CodeMirror-activeline-background { background: #3c3836; }
.cm-s-gruvbox-dark .CodeMirror-matchingbracket { background: #928374; color:#282828 !important; }
.cm-s-gruvbox-dark span.cm-builtin { color: #fe8019; }
.cm-s-gruvbox-dark span.cm-tag { color: #fe8019; }
+34
View File
@@ -0,0 +1,34 @@
/*
Name: Hopscotch
Author: Jan T. Sott
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
*/
.cm-s-hopscotch.CodeMirror {background: #322931; color: #d5d3d5;}
.cm-s-hopscotch div.CodeMirror-selected {background: #433b42 !important;}
.cm-s-hopscotch .CodeMirror-gutters {background: #322931; border-right: 0px;}
.cm-s-hopscotch .CodeMirror-linenumber {color: #797379;}
.cm-s-hopscotch .CodeMirror-cursor {border-left: 1px solid #989498 !important;}
.cm-s-hopscotch span.cm-comment {color: #b33508;}
.cm-s-hopscotch span.cm-atom {color: #c85e7c;}
.cm-s-hopscotch span.cm-number {color: #c85e7c;}
.cm-s-hopscotch span.cm-property, .cm-s-hopscotch span.cm-attribute {color: #8fc13e;}
.cm-s-hopscotch span.cm-keyword {color: #dd464c;}
.cm-s-hopscotch span.cm-string {color: #fdcc59;}
.cm-s-hopscotch span.cm-variable {color: #8fc13e;}
.cm-s-hopscotch span.cm-variable-2 {color: #1290bf;}
.cm-s-hopscotch span.cm-def {color: #fd8b19;}
.cm-s-hopscotch span.cm-error {background: #dd464c; color: #989498;}
.cm-s-hopscotch span.cm-bracket {color: #d5d3d5;}
.cm-s-hopscotch span.cm-tag {color: #dd464c;}
.cm-s-hopscotch span.cm-link {color: #c85e7c;}
.cm-s-hopscotch .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;}
.cm-s-hopscotch .CodeMirror-activeline-background { background: #302020; }
+43
View File
@@ -0,0 +1,43 @@
/*
ICEcoder default theme by Matt Pass, used in code editor available at https://icecoder.net
*/
.cm-s-icecoder { color: #666; background: #1d1d1b; }
.cm-s-icecoder span.cm-keyword { color: #eee; font-weight:bold; } /* off-white 1 */
.cm-s-icecoder span.cm-atom { color: #e1c76e; } /* yellow */
.cm-s-icecoder span.cm-number { color: #6cb5d9; } /* blue */
.cm-s-icecoder span.cm-def { color: #b9ca4a; } /* green */
.cm-s-icecoder span.cm-variable { color: #6cb5d9; } /* blue */
.cm-s-icecoder span.cm-variable-2 { color: #cc1e5c; } /* pink */
.cm-s-icecoder span.cm-variable-3, .cm-s-icecoder span.cm-type { color: #f9602c; } /* orange */
.cm-s-icecoder span.cm-property { color: #eee; } /* off-white 1 */
.cm-s-icecoder span.cm-operator { color: #9179bb; } /* purple */
.cm-s-icecoder span.cm-comment { color: #97a3aa; } /* grey-blue */
.cm-s-icecoder span.cm-string { color: #b9ca4a; } /* green */
.cm-s-icecoder span.cm-string-2 { color: #6cb5d9; } /* blue */
.cm-s-icecoder span.cm-meta { color: #555; } /* grey */
.cm-s-icecoder span.cm-qualifier { color: #555; } /* grey */
.cm-s-icecoder span.cm-builtin { color: #214e7b; } /* bright blue */
.cm-s-icecoder span.cm-bracket { color: #cc7; } /* grey-yellow */
.cm-s-icecoder span.cm-tag { color: #e8e8e8; } /* off-white 2 */
.cm-s-icecoder span.cm-attribute { color: #099; } /* teal */
.cm-s-icecoder span.cm-header { color: #6a0d6a; } /* purple-pink */
.cm-s-icecoder span.cm-quote { color: #186718; } /* dark green */
.cm-s-icecoder span.cm-hr { color: #888; } /* mid-grey */
.cm-s-icecoder span.cm-link { color: #e1c76e; } /* yellow */
.cm-s-icecoder span.cm-error { color: #d00; } /* red */
.cm-s-icecoder .CodeMirror-cursor { border-left: 1px solid white; }
.cm-s-icecoder div.CodeMirror-selected { color: #fff; background: #037; }
.cm-s-icecoder .CodeMirror-gutters { background: #1d1d1b; min-width: 41px; border-right: 0; }
.cm-s-icecoder .CodeMirror-linenumber { color: #555; cursor: default; }
.cm-s-icecoder .CodeMirror-matchingbracket { color: #fff !important; background: #555 !important; }
.cm-s-icecoder .CodeMirror-activeline-background { background: #000; }
+42
View File
@@ -0,0 +1,42 @@
/**
Name: IDEA default theme
From IntelliJ IDEA by JetBrains
*/
.cm-s-idea span.cm-meta { color: #808000; }
.cm-s-idea span.cm-number { color: #0000FF; }
.cm-s-idea span.cm-keyword { line-height: 1em; font-weight: bold; color: #000080; }
.cm-s-idea span.cm-atom { font-weight: bold; color: #000080; }
.cm-s-idea span.cm-def { color: #000000; }
.cm-s-idea span.cm-variable { color: black; }
.cm-s-idea span.cm-variable-2 { color: black; }
.cm-s-idea span.cm-variable-3, .cm-s-idea span.cm-type { color: black; }
.cm-s-idea span.cm-property { color: black; }
.cm-s-idea span.cm-operator { color: black; }
.cm-s-idea span.cm-comment { color: #808080; }
.cm-s-idea span.cm-string { color: #008000; }
.cm-s-idea span.cm-string-2 { color: #008000; }
.cm-s-idea span.cm-qualifier { color: #555; }
.cm-s-idea span.cm-error { color: #FF0000; }
.cm-s-idea span.cm-attribute { color: #0000FF; }
.cm-s-idea span.cm-tag { color: #000080; }
.cm-s-idea span.cm-link { color: #0000FF; }
.cm-s-idea .CodeMirror-activeline-background { background: #FFFAE3; }
.cm-s-idea span.cm-builtin { color: #30a; }
.cm-s-idea span.cm-bracket { color: #cc7; }
.cm-s-idea { font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;}
.cm-s-idea .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
.CodeMirror-hints.idea {
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
color: #616569;
background-color: #ebf3fd !important;
}
.CodeMirror-hints.idea .CodeMirror-hint-active {
background-color: #a2b8c9 !important;
color: #5c6065 !important;
}
+34
View File
@@ -0,0 +1,34 @@
/*
Name: Isotope
Author: David Desandro / Jan T. Sott
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
*/
.cm-s-isotope.CodeMirror {background: #000000; color: #e0e0e0;}
.cm-s-isotope div.CodeMirror-selected {background: #404040 !important;}
.cm-s-isotope .CodeMirror-gutters {background: #000000; border-right: 0px;}
.cm-s-isotope .CodeMirror-linenumber {color: #808080;}
.cm-s-isotope .CodeMirror-cursor {border-left: 1px solid #c0c0c0 !important;}
.cm-s-isotope span.cm-comment {color: #3300ff;}
.cm-s-isotope span.cm-atom {color: #cc00ff;}
.cm-s-isotope span.cm-number {color: #cc00ff;}
.cm-s-isotope span.cm-property, .cm-s-isotope span.cm-attribute {color: #33ff00;}
.cm-s-isotope span.cm-keyword {color: #ff0000;}
.cm-s-isotope span.cm-string {color: #ff0099;}
.cm-s-isotope span.cm-variable {color: #33ff00;}
.cm-s-isotope span.cm-variable-2 {color: #0066ff;}
.cm-s-isotope span.cm-def {color: #ff9900;}
.cm-s-isotope span.cm-error {background: #ff0000; color: #c0c0c0;}
.cm-s-isotope span.cm-bracket {color: #e0e0e0;}
.cm-s-isotope span.cm-tag {color: #ff0000;}
.cm-s-isotope span.cm-link {color: #cc00ff;}
.cm-s-isotope .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;}
.cm-s-isotope .CodeMirror-activeline-background { background: #202020; }
+47
View File
@@ -0,0 +1,47 @@
/*
http://lesscss.org/ dark theme
Ported to CodeMirror by Peter Kroon
*/
.cm-s-lesser-dark {
line-height: 1.3em;
}
.cm-s-lesser-dark.CodeMirror { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; }
.cm-s-lesser-dark div.CodeMirror-selected { background: #45443B; } /* 33322B*/
.cm-s-lesser-dark .CodeMirror-line::selection, .cm-s-lesser-dark .CodeMirror-line > span::selection, .cm-s-lesser-dark .CodeMirror-line > span > span::selection { background: rgba(69, 68, 59, .99); }
.cm-s-lesser-dark .CodeMirror-line::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(69, 68, 59, .99); }
.cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white; }
.cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/
.cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/
.cm-s-lesser-dark .CodeMirror-gutters { background: #262626; border-right:1px solid #aaa; }
.cm-s-lesser-dark .CodeMirror-guttermarker { color: #599eff; }
.cm-s-lesser-dark .CodeMirror-guttermarker-subtle { color: #777; }
.cm-s-lesser-dark .CodeMirror-linenumber { color: #777; }
.cm-s-lesser-dark span.cm-header { color: #a0a; }
.cm-s-lesser-dark span.cm-quote { color: #090; }
.cm-s-lesser-dark span.cm-keyword { color: #599eff; }
.cm-s-lesser-dark span.cm-atom { color: #C2B470; }
.cm-s-lesser-dark span.cm-number { color: #B35E4D; }
.cm-s-lesser-dark span.cm-def { color: white; }
.cm-s-lesser-dark span.cm-variable { color:#D9BF8C; }
.cm-s-lesser-dark span.cm-variable-2 { color: #669199; }
.cm-s-lesser-dark span.cm-variable-3, .cm-s-lesser-dark span.cm-type { color: white; }
.cm-s-lesser-dark span.cm-property { color: #92A75C; }
.cm-s-lesser-dark span.cm-operator { color: #92A75C; }
.cm-s-lesser-dark span.cm-comment { color: #666; }
.cm-s-lesser-dark span.cm-string { color: #BCD279; }
.cm-s-lesser-dark span.cm-string-2 { color: #f50; }
.cm-s-lesser-dark span.cm-meta { color: #738C73; }
.cm-s-lesser-dark span.cm-qualifier { color: #555; }
.cm-s-lesser-dark span.cm-builtin { color: #ff9e59; }
.cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; }
.cm-s-lesser-dark span.cm-tag { color: #669199; }
.cm-s-lesser-dark span.cm-attribute { color: #81a4d5; }
.cm-s-lesser-dark span.cm-hr { color: #999; }
.cm-s-lesser-dark span.cm-link { color: #7070E6; }
.cm-s-lesser-dark span.cm-error { color: #9d1e15; }
.cm-s-lesser-dark .CodeMirror-activeline-background { background: #3C3A3A; }
.cm-s-lesser-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }
+95
View File
@@ -0,0 +1,95 @@
.cm-s-liquibyte.CodeMirror {
background-color: #000;
color: #fff;
line-height: 1.2em;
font-size: 1em;
}
.cm-s-liquibyte .CodeMirror-focused .cm-matchhighlight {
text-decoration: underline;
text-decoration-color: #0f0;
text-decoration-style: wavy;
}
.cm-s-liquibyte .cm-trailingspace {
text-decoration: line-through;
text-decoration-color: #f00;
text-decoration-style: dotted;
}
.cm-s-liquibyte .cm-tab {
text-decoration: line-through;
text-decoration-color: #404040;
text-decoration-style: dotted;
}
.cm-s-liquibyte .CodeMirror-gutters { background-color: #262626; border-right: 1px solid #505050; padding-right: 0.8em; }
.cm-s-liquibyte .CodeMirror-gutter-elt div { font-size: 1.2em; }
.cm-s-liquibyte .CodeMirror-guttermarker { }
.cm-s-liquibyte .CodeMirror-guttermarker-subtle { }
.cm-s-liquibyte .CodeMirror-linenumber { color: #606060; padding-left: 0; }
.cm-s-liquibyte .CodeMirror-cursor { border-left: 1px solid #eee; }
.cm-s-liquibyte span.cm-comment { color: #008000; }
.cm-s-liquibyte span.cm-def { color: #ffaf40; font-weight: bold; }
.cm-s-liquibyte span.cm-keyword { color: #c080ff; font-weight: bold; }
.cm-s-liquibyte span.cm-builtin { color: #ffaf40; font-weight: bold; }
.cm-s-liquibyte span.cm-variable { color: #5967ff; font-weight: bold; }
.cm-s-liquibyte span.cm-string { color: #ff8000; }
.cm-s-liquibyte span.cm-number { color: #0f0; font-weight: bold; }
.cm-s-liquibyte span.cm-atom { color: #bf3030; font-weight: bold; }
.cm-s-liquibyte span.cm-variable-2 { color: #007f7f; font-weight: bold; }
.cm-s-liquibyte span.cm-variable-3, .cm-s-liquibyte span.cm-type { color: #c080ff; font-weight: bold; }
.cm-s-liquibyte span.cm-property { color: #999; font-weight: bold; }
.cm-s-liquibyte span.cm-operator { color: #fff; }
.cm-s-liquibyte span.cm-meta { color: #0f0; }
.cm-s-liquibyte span.cm-qualifier { color: #fff700; font-weight: bold; }
.cm-s-liquibyte span.cm-bracket { color: #cc7; }
.cm-s-liquibyte span.cm-tag { color: #ff0; font-weight: bold; }
.cm-s-liquibyte span.cm-attribute { color: #c080ff; font-weight: bold; }
.cm-s-liquibyte span.cm-error { color: #f00; }
.cm-s-liquibyte div.CodeMirror-selected { background-color: rgba(255, 0, 0, 0.25); }
.cm-s-liquibyte span.cm-compilation { background-color: rgba(255, 255, 255, 0.12); }
.cm-s-liquibyte .CodeMirror-activeline-background { background-color: rgba(0, 255, 0, 0.15); }
/* Default styles for common addons */
.cm-s-liquibyte .CodeMirror span.CodeMirror-matchingbracket { color: #0f0; font-weight: bold; }
.cm-s-liquibyte .CodeMirror span.CodeMirror-nonmatchingbracket { color: #f00; font-weight: bold; }
.CodeMirror-matchingtag { background-color: rgba(150, 255, 0, .3); }
/* Scrollbars */
/* Simple */
.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div:hover, .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div:hover {
background-color: rgba(80, 80, 80, .7);
}
.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div, .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div {
background-color: rgba(80, 80, 80, .3);
border: 1px solid #404040;
border-radius: 5px;
}
.cm-s-liquibyte div.CodeMirror-simplescroll-vertical div {
border-top: 1px solid #404040;
border-bottom: 1px solid #404040;
}
.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div {
border-left: 1px solid #404040;
border-right: 1px solid #404040;
}
.cm-s-liquibyte div.CodeMirror-simplescroll-vertical {
background-color: #262626;
}
.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal {
background-color: #262626;
border-top: 1px solid #404040;
}
/* Overlay */
.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div, div.CodeMirror-overlayscroll-vertical div {
background-color: #404040;
border-radius: 5px;
}
.cm-s-liquibyte div.CodeMirror-overlayscroll-vertical div {
border: 1px solid #404040;
}
.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div {
border: 1px solid #404040;
}
+37
View File
@@ -0,0 +1,37 @@
/*
Name: lucario
Author: Raphael Amorim
Original Lucario color scheme (https://github.com/raphamorim/lucario)
*/
.cm-s-lucario.CodeMirror, .cm-s-lucario .CodeMirror-gutters {
background-color: #2b3e50 !important;
color: #f8f8f2 !important;
border: none;
}
.cm-s-lucario .CodeMirror-gutters { color: #2b3e50; }
.cm-s-lucario .CodeMirror-cursor { border-left: solid thin #E6C845; }
.cm-s-lucario .CodeMirror-linenumber { color: #f8f8f2; }
.cm-s-lucario .CodeMirror-selected { background: #243443; }
.cm-s-lucario .CodeMirror-line::selection, .cm-s-lucario .CodeMirror-line > span::selection, .cm-s-lucario .CodeMirror-line > span > span::selection { background: #243443; }
.cm-s-lucario .CodeMirror-line::-moz-selection, .cm-s-lucario .CodeMirror-line > span::-moz-selection, .cm-s-lucario .CodeMirror-line > span > span::-moz-selection { background: #243443; }
.cm-s-lucario span.cm-comment { color: #5c98cd; }
.cm-s-lucario span.cm-string, .cm-s-lucario span.cm-string-2 { color: #E6DB74; }
.cm-s-lucario span.cm-number { color: #ca94ff; }
.cm-s-lucario span.cm-variable { color: #f8f8f2; }
.cm-s-lucario span.cm-variable-2 { color: #f8f8f2; }
.cm-s-lucario span.cm-def { color: #72C05D; }
.cm-s-lucario span.cm-operator { color: #66D9EF; }
.cm-s-lucario span.cm-keyword { color: #ff6541; }
.cm-s-lucario span.cm-atom { color: #bd93f9; }
.cm-s-lucario span.cm-meta { color: #f8f8f2; }
.cm-s-lucario span.cm-tag { color: #ff6541; }
.cm-s-lucario span.cm-attribute { color: #66D9EF; }
.cm-s-lucario span.cm-qualifier { color: #72C05D; }
.cm-s-lucario span.cm-property { color: #f8f8f2; }
.cm-s-lucario span.cm-builtin { color: #72C05D; }
.cm-s-lucario span.cm-variable-3, .cm-s-lucario span.cm-type { color: #ffb86c; }
.cm-s-lucario .CodeMirror-activeline-background { background: #243443; }
.cm-s-lucario .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }
@@ -0,0 +1,135 @@
/*
Name: material
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
.cm-s-material-darker.CodeMirror {
background-color: #212121;
color: #EEFFFF;
}
.cm-s-material-darker .CodeMirror-gutters {
background: #212121;
color: #545454;
border: none;
}
.cm-s-material-darker .CodeMirror-guttermarker,
.cm-s-material-darker .CodeMirror-guttermarker-subtle,
.cm-s-material-darker .CodeMirror-linenumber {
color: #545454;
}
.cm-s-material-darker .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-material-darker div.CodeMirror-selected {
background: rgba(97, 97, 97, 0.2);
}
.cm-s-material-darker.CodeMirror-focused div.CodeMirror-selected {
background: rgba(97, 97, 97, 0.2);
}
.cm-s-material-darker .CodeMirror-line::selection,
.cm-s-material-darker .CodeMirror-line>span::selection,
.cm-s-material-darker .CodeMirror-line>span>span::selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-darker .CodeMirror-line::-moz-selection,
.cm-s-material-darker .CodeMirror-line>span::-moz-selection,
.cm-s-material-darker .CodeMirror-line>span>span::-moz-selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-darker .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.5);
}
.cm-s-material-darker .cm-keyword {
color: #C792EA;
}
.cm-s-material-darker .cm-operator {
color: #89DDFF;
}
.cm-s-material-darker .cm-variable-2 {
color: #EEFFFF;
}
.cm-s-material-darker .cm-variable-3,
.cm-s-material-darker .cm-type {
color: #f07178;
}
.cm-s-material-darker .cm-builtin {
color: #FFCB6B;
}
.cm-s-material-darker .cm-atom {
color: #F78C6C;
}
.cm-s-material-darker .cm-number {
color: #FF5370;
}
.cm-s-material-darker .cm-def {
color: #82AAFF;
}
.cm-s-material-darker .cm-string {
color: #C3E88D;
}
.cm-s-material-darker .cm-string-2 {
color: #f07178;
}
.cm-s-material-darker .cm-comment {
color: #545454;
}
.cm-s-material-darker .cm-variable {
color: #f07178;
}
.cm-s-material-darker .cm-tag {
color: #FF5370;
}
.cm-s-material-darker .cm-meta {
color: #FFCB6B;
}
.cm-s-material-darker .cm-attribute {
color: #C792EA;
}
.cm-s-material-darker .cm-property {
color: #C792EA;
}
.cm-s-material-darker .cm-qualifier {
color: #DECB6B;
}
.cm-s-material-darker .cm-variable-3,
.cm-s-material-darker .cm-type {
color: #DECB6B;
}
.cm-s-material-darker .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-material-darker .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
+135
View File
@@ -0,0 +1,135 @@
/*
Name: material
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
.cm-s-material-ocean.CodeMirror {
background-color: #0F111A;
color: #8F93A2;
}
.cm-s-material-ocean .CodeMirror-gutters {
background: #0F111A;
color: #464B5D;
border: none;
}
.cm-s-material-ocean .CodeMirror-guttermarker,
.cm-s-material-ocean .CodeMirror-guttermarker-subtle,
.cm-s-material-ocean .CodeMirror-linenumber {
color: #464B5D;
}
.cm-s-material-ocean .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-material-ocean div.CodeMirror-selected {
background: rgba(113, 124, 180, 0.2);
}
.cm-s-material-ocean.CodeMirror-focused div.CodeMirror-selected {
background: rgba(113, 124, 180, 0.2);
}
.cm-s-material-ocean .CodeMirror-line::selection,
.cm-s-material-ocean .CodeMirror-line>span::selection,
.cm-s-material-ocean .CodeMirror-line>span>span::selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-ocean .CodeMirror-line::-moz-selection,
.cm-s-material-ocean .CodeMirror-line>span::-moz-selection,
.cm-s-material-ocean .CodeMirror-line>span>span::-moz-selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-ocean .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.5);
}
.cm-s-material-ocean .cm-keyword {
color: #C792EA;
}
.cm-s-material-ocean .cm-operator {
color: #89DDFF;
}
.cm-s-material-ocean .cm-variable-2 {
color: #EEFFFF;
}
.cm-s-material-ocean .cm-variable-3,
.cm-s-material-ocean .cm-type {
color: #f07178;
}
.cm-s-material-ocean .cm-builtin {
color: #FFCB6B;
}
.cm-s-material-ocean .cm-atom {
color: #F78C6C;
}
.cm-s-material-ocean .cm-number {
color: #FF5370;
}
.cm-s-material-ocean .cm-def {
color: #82AAFF;
}
.cm-s-material-ocean .cm-string {
color: #C3E88D;
}
.cm-s-material-ocean .cm-string-2 {
color: #f07178;
}
.cm-s-material-ocean .cm-comment {
color: #464B5D;
}
.cm-s-material-ocean .cm-variable {
color: #f07178;
}
.cm-s-material-ocean .cm-tag {
color: #FF5370;
}
.cm-s-material-ocean .cm-meta {
color: #FFCB6B;
}
.cm-s-material-ocean .cm-attribute {
color: #C792EA;
}
.cm-s-material-ocean .cm-property {
color: #C792EA;
}
.cm-s-material-ocean .cm-qualifier {
color: #DECB6B;
}
.cm-s-material-ocean .cm-variable-3,
.cm-s-material-ocean .cm-type {
color: #DECB6B;
}
.cm-s-material-ocean .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-material-ocean .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
@@ -0,0 +1,135 @@
/*
Name: material
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
.cm-s-material-palenight.CodeMirror {
background-color: #292D3E;
color: #A6ACCD;
}
.cm-s-material-palenight .CodeMirror-gutters {
background: #292D3E;
color: #676E95;
border: none;
}
.cm-s-material-palenight .CodeMirror-guttermarker,
.cm-s-material-palenight .CodeMirror-guttermarker-subtle,
.cm-s-material-palenight .CodeMirror-linenumber {
color: #676E95;
}
.cm-s-material-palenight .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-material-palenight div.CodeMirror-selected {
background: rgba(113, 124, 180, 0.2);
}
.cm-s-material-palenight.CodeMirror-focused div.CodeMirror-selected {
background: rgba(113, 124, 180, 0.2);
}
.cm-s-material-palenight .CodeMirror-line::selection,
.cm-s-material-palenight .CodeMirror-line>span::selection,
.cm-s-material-palenight .CodeMirror-line>span>span::selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-palenight .CodeMirror-line::-moz-selection,
.cm-s-material-palenight .CodeMirror-line>span::-moz-selection,
.cm-s-material-palenight .CodeMirror-line>span>span::-moz-selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-palenight .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.5);
}
.cm-s-material-palenight .cm-keyword {
color: #C792EA;
}
.cm-s-material-palenight .cm-operator {
color: #89DDFF;
}
.cm-s-material-palenight .cm-variable-2 {
color: #EEFFFF;
}
.cm-s-material-palenight .cm-variable-3,
.cm-s-material-palenight .cm-type {
color: #f07178;
}
.cm-s-material-palenight .cm-builtin {
color: #FFCB6B;
}
.cm-s-material-palenight .cm-atom {
color: #F78C6C;
}
.cm-s-material-palenight .cm-number {
color: #FF5370;
}
.cm-s-material-palenight .cm-def {
color: #82AAFF;
}
.cm-s-material-palenight .cm-string {
color: #C3E88D;
}
.cm-s-material-palenight .cm-string-2 {
color: #f07178;
}
.cm-s-material-palenight .cm-comment {
color: #676E95;
}
.cm-s-material-palenight .cm-variable {
color: #f07178;
}
.cm-s-material-palenight .cm-tag {
color: #FF5370;
}
.cm-s-material-palenight .cm-meta {
color: #FFCB6B;
}
.cm-s-material-palenight .cm-attribute {
color: #C792EA;
}
.cm-s-material-palenight .cm-property {
color: #C792EA;
}
.cm-s-material-palenight .cm-qualifier {
color: #DECB6B;
}
.cm-s-material-palenight .cm-variable-3,
.cm-s-material-palenight .cm-type {
color: #DECB6B;
}
.cm-s-material-palenight .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-material-palenight .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
+135
View File
@@ -0,0 +1,135 @@
/*
Name: material
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
.cm-s-material.CodeMirror {
background-color: #263238;
color: #EEFFFF;
}
.cm-s-material .CodeMirror-gutters {
background: #263238;
color: #546E7A;
border: none;
}
.cm-s-material .CodeMirror-guttermarker,
.cm-s-material .CodeMirror-guttermarker-subtle,
.cm-s-material .CodeMirror-linenumber {
color: #546E7A;
}
.cm-s-material .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-material div.CodeMirror-selected {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material.CodeMirror-focused div.CodeMirror-selected {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-line::selection,
.cm-s-material .CodeMirror-line>span::selection,
.cm-s-material .CodeMirror-line>span>span::selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-line::-moz-selection,
.cm-s-material .CodeMirror-line>span::-moz-selection,
.cm-s-material .CodeMirror-line>span>span::-moz-selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.5);
}
.cm-s-material .cm-keyword {
color: #C792EA;
}
.cm-s-material .cm-operator {
color: #89DDFF;
}
.cm-s-material .cm-variable-2 {
color: #EEFFFF;
}
.cm-s-material .cm-variable-3,
.cm-s-material .cm-type {
color: #f07178;
}
.cm-s-material .cm-builtin {
color: #FFCB6B;
}
.cm-s-material .cm-atom {
color: #F78C6C;
}
.cm-s-material .cm-number {
color: #FF5370;
}
.cm-s-material .cm-def {
color: #82AAFF;
}
.cm-s-material .cm-string {
color: #C3E88D;
}
.cm-s-material .cm-string-2 {
color: #f07178;
}
.cm-s-material .cm-comment {
color: #546E7A;
}
.cm-s-material .cm-variable {
color: #f07178;
}
.cm-s-material .cm-tag {
color: #FF5370;
}
.cm-s-material .cm-meta {
color: #FFCB6B;
}
.cm-s-material .cm-attribute {
color: #C792EA;
}
.cm-s-material .cm-property {
color: #C792EA;
}
.cm-s-material .cm-qualifier {
color: #DECB6B;
}
.cm-s-material .cm-variable-3,
.cm-s-material .cm-type {
color: #DECB6B;
}
.cm-s-material .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-material .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
+37
View File
@@ -0,0 +1,37 @@
/****************************************************************/
/* Based on mbonaci's Brackets mbo theme */
/* https://github.com/mbonaci/global/blob/master/Mbo.tmTheme */
/* Create your own: http://tmtheme-editor.herokuapp.com */
/****************************************************************/
.cm-s-mbo.CodeMirror { background: #2c2c2c; color: #ffffec; }
.cm-s-mbo div.CodeMirror-selected { background: #716C62; }
.cm-s-mbo .CodeMirror-line::selection, .cm-s-mbo .CodeMirror-line > span::selection, .cm-s-mbo .CodeMirror-line > span > span::selection { background: rgba(113, 108, 98, .99); }
.cm-s-mbo .CodeMirror-line::-moz-selection, .cm-s-mbo .CodeMirror-line > span::-moz-selection, .cm-s-mbo .CodeMirror-line > span > span::-moz-selection { background: rgba(113, 108, 98, .99); }
.cm-s-mbo .CodeMirror-gutters { background: #4e4e4e; border-right: 0px; }
.cm-s-mbo .CodeMirror-guttermarker { color: white; }
.cm-s-mbo .CodeMirror-guttermarker-subtle { color: grey; }
.cm-s-mbo .CodeMirror-linenumber { color: #dadada; }
.cm-s-mbo .CodeMirror-cursor { border-left: 1px solid #ffffec; }
.cm-s-mbo span.cm-comment { color: #95958a; }
.cm-s-mbo span.cm-atom { color: #00a8c6; }
.cm-s-mbo span.cm-number { color: #00a8c6; }
.cm-s-mbo span.cm-property, .cm-s-mbo span.cm-attribute { color: #9ddfe9; }
.cm-s-mbo span.cm-keyword { color: #ffb928; }
.cm-s-mbo span.cm-string { color: #ffcf6c; }
.cm-s-mbo span.cm-string.cm-property { color: #ffffec; }
.cm-s-mbo span.cm-variable { color: #ffffec; }
.cm-s-mbo span.cm-variable-2 { color: #00a8c6; }
.cm-s-mbo span.cm-def { color: #ffffec; }
.cm-s-mbo span.cm-bracket { color: #fffffc; font-weight: bold; }
.cm-s-mbo span.cm-tag { color: #9ddfe9; }
.cm-s-mbo span.cm-link { color: #f54b07; }
.cm-s-mbo span.cm-error { border-bottom: #636363; color: #ffffec; }
.cm-s-mbo span.cm-qualifier { color: #ffffec; }
.cm-s-mbo .CodeMirror-activeline-background { background: #494b41; }
.cm-s-mbo .CodeMirror-matchingbracket { color: #ffb928 !important; }
.cm-s-mbo .CodeMirror-matchingtag { background: rgba(255, 255, 255, .37); }
+46
View File
@@ -0,0 +1,46 @@
/*
MDN-LIKE Theme - Mozilla
Ported to CodeMirror by Peter Kroon <plakroon@gmail.com>
Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues
GitHub: @peterkroon
The mdn-like theme is inspired on the displayed code examples at: https://developer.mozilla.org/en-US/docs/Web/CSS/animation
*/
.cm-s-mdn-like.CodeMirror { color: #999; background-color: #fff; }
.cm-s-mdn-like div.CodeMirror-selected { background: #cfc; }
.cm-s-mdn-like .CodeMirror-line::selection, .cm-s-mdn-like .CodeMirror-line > span::selection, .cm-s-mdn-like .CodeMirror-line > span > span::selection { background: #cfc; }
.cm-s-mdn-like .CodeMirror-line::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span > span::-moz-selection { background: #cfc; }
.cm-s-mdn-like .CodeMirror-gutters { background: #f8f8f8; border-left: 6px solid rgba(0,83,159,0.65); color: #333; }
.cm-s-mdn-like .CodeMirror-linenumber { color: #aaa; padding-left: 8px; }
.cm-s-mdn-like .CodeMirror-cursor { border-left: 2px solid #222; }
.cm-s-mdn-like .cm-keyword { color: #6262FF; }
.cm-s-mdn-like .cm-atom { color: #F90; }
.cm-s-mdn-like .cm-number { color: #ca7841; }
.cm-s-mdn-like .cm-def { color: #8DA6CE; }
.cm-s-mdn-like span.cm-variable-2, .cm-s-mdn-like span.cm-tag { color: #690; }
.cm-s-mdn-like span.cm-variable-3, .cm-s-mdn-like span.cm-def, .cm-s-mdn-like span.cm-type { color: #07a; }
.cm-s-mdn-like .cm-variable { color: #07a; }
.cm-s-mdn-like .cm-property { color: #905; }
.cm-s-mdn-like .cm-qualifier { color: #690; }
.cm-s-mdn-like .cm-operator { color: #cda869; }
.cm-s-mdn-like .cm-comment { color:#777; font-weight:normal; }
.cm-s-mdn-like .cm-string { color:#07a; font-style:italic; }
.cm-s-mdn-like .cm-string-2 { color:#bd6b18; } /*?*/
.cm-s-mdn-like .cm-meta { color: #000; } /*?*/
.cm-s-mdn-like .cm-builtin { color: #9B7536; } /*?*/
.cm-s-mdn-like .cm-tag { color: #997643; }
.cm-s-mdn-like .cm-attribute { color: #d6bb6d; } /*?*/
.cm-s-mdn-like .cm-header { color: #FF6400; }
.cm-s-mdn-like .cm-hr { color: #AEAEAE; }
.cm-s-mdn-like .cm-link { color:#ad9361; font-style:italic; text-decoration:none; }
.cm-s-mdn-like .cm-error { border-bottom: 1px solid red; }
div.cm-s-mdn-like .CodeMirror-activeline-background { background: #efefff; }
div.cm-s-mdn-like span.CodeMirror-matchingbracket { outline:1px solid grey; color: inherit; }
.cm-s-mdn-like.CodeMirror { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFcAAAAyCAYAAAAp8UeFAAAHvklEQVR42s2b63bcNgyEQZCSHCdt2vd/0tWF7I+Q6XgMXiTtuvU5Pl57ZQKkKHzEAOtF5KeIJBGJ8uvL599FRFREZhFx8DeXv8trn68RuGaC8TRfo3SNp9dlDDHedyLyTUTeRWStXKPZrjtpZxaRw5hPqozRs1N8/enzIiQRWcCgy4MUA0f+XWliDhyL8Lfyvx7ei/Ae3iQFHyw7U/59pQVIMEEPEz0G7XiwdRjzSfC3UTtz9vchIntxvry5iMgfIhJoEflOz2CQr3F5h/HfeFe+GTdLaKcu9L8LTeQb/R/7GgbsfKedyNdoHsN31uRPWrfZ5wsj/NzzRQHuToIdU3ahwnsKPxXCjJITuOsi7XLc7SG/v5GdALs7wf8JjTFiB5+QvTEfRyGOfX3Lrx8wxyQi3sNq46O7QahQiCsRFgqddjBouVEHOKDgXAQHD9gJCr5sMKkEdjwsarG/ww3BMHBU7OBjXnzdyY7SfCxf5/z6ATccrwlKuwC/jhznnPF4CgVzhhVf4xp2EixcBActO75iZ8/fM9zAs2OMzKdslgXWJ9XG8PQoOAMA5fGcsvORgv0doBXyHrCwfLJAOwo71QLNkb8n2Pl6EWiR7OCibtkPaz4Kc/0NNAze2gju3zOwekALDaCFPI5vjPFmgGY5AZqyGEvH1x7QfIb8YtxMnA/b+QQ0aQDAwc6JMFg8CbQZ4qoYEEHbRwNojuK3EHwd7VALSgq+MNDKzfT58T8qdpADrgW0GmgcAS1lhzztJmkAzcPNOQbsWEALBDSlMKUG0Eq4CLAQWvEVQ9WU57gZJwZtgPO3r9oBTQ9WO8TjqXINx8R0EYpiZEUWOF3FxkbJkgU9B2f41YBrIj5ZfsQa0M5kTgiAAqM3ShXLgu8XMqcrQBvJ0CL5pnTsfMB13oB8athpAq2XOQmcGmoACCLydx7nToa23ATaSIY2ichfOdPTGxlasXMLaL0MLZAOwAKIM+y8CmicobGdCcbbK9DzN+yYGVoNNI5iUKTMyYOjPse4A8SM1MmcXgU0toOq1yO/v8FOxlASyc7TgeYaAMBJHcY1CcCwGI/TK4AmDbDyKYBBtFUkRwto8gygiQEaByFgJ00BH2M8JWwQS1nafDXQCidWyOI8AcjDCSjCLk8ngObuAm3JAHAdubAmOaK06V8MNEsKPJOhobSprwQa6gD7DclRQdqcwL4zxqgBrQcabUiBLclRDKAlWp+etPkBaNMA0AKlrHwTdEByZAA4GM+SNluSY6wAzcMNewxmgig5Ks0nkrSpBvSaQHMdKTBAnLojOdYyGpQ254602ZILPdTD1hdlggdIm74jbTp8vDwF5ZYUeLWGJpWsh6XNyXgcYwVoJQTEhhTYkxzZjiU5npU2TaB979TQehlaAVq4kaGpiPwwwLkYUuBbQwocyQTv1tA0+1UFWoJF3iv1oq+qoSk8EQdJmwHkziIF7oOZk14EGitibAdjLYYK78H5vZOhtWpoI0ATGHs0Q8OMb4Ey+2bU2UYztCtA0wFAs7TplGLRVQCcqaFdGSPCeTI1QNIC52iWNzof6Uib7xjEp07mNNoUYmVosVItHrHzRlLgBn9LFyRHaQCtVUMbtTNhoXWiTOO9k/V8BdAc1Oq0ArSQs6/5SU0hckNy9NnXqQY0PGYo5dWJ7nINaN6o958FWin27aBaWRka1r5myvLOAm0j30eBJqCxHLReVclxhxOEN2JfDWjxBtAC7MIH1fVaGdoOp4qJYDgKtKPSFNID2gSnGldrCqkFZ+5UeQXQBIRrSwocbdZYQT/2LwRahBPBXoHrB8nxaGROST62DKUbQOMMzZIC9abkuELfQzQALWTnDNAm8KHWFOJgJ5+SHIvTPcmx1xQyZRhNL5Qci689aXMEaN/uNIWkEwDAvFpOZmgsBaaGnbs1NPa1Jm32gBZAIh1pCtG7TSH4aE0y1uVY4uqoFPisGlpP2rSA5qTecWn5agK6BzSpgAyD+wFaqhnYoSZ1Vwr8CmlTQbrcO3ZaX0NAEyMbYaAlyquFoLKK3SPby9CeVUPThrSJmkCAE0CrKUQadi4DrdSlWhmah0YL9z9vClH59YGbHx1J8VZTyAjQepJjmXwAKTDQI3omc3p1U4gDUf6RfcdYfrUp5ClAi2J3Ba6UOXGo+K+bQrjjssitG2SJzshaLwMtXgRagUNpYYoVkMSBLM+9GGiJZMvduG6DRZ4qc04DMPtQQxOjEtACmhO7K1AbNbQDEggZyJwscFpAGwENhoBeUwh3bWolhe8BTYVKxQEWrSUn/uhcM5KhvUu/+eQu0Lzhi+VrK0PrZZNDQKs9cpYUuFYgMVpD4/NxenJTiMCNqdUEUf1qZWjppLT5qSkkUZbCwkbZMSuVnu80hfSkzRbQeqCZSAh6huR4VtoM2gHAlLf72smuWgE+VV7XpE25Ab2WFDgyhnSuKbs4GuGzCjR+tIoUuMFg3kgcWKLTwRqanJQ2W00hAsenfaApRC42hbCvK1SlE0HtE9BGgneJO+ELamitD1YjjOYnNYVcraGhtKkW0EqVVeDx733I2NH581k1NNxNLG0i0IJ8/NjVaOZ0tYZ2Vtr0Xv7tPV3hkWp9EFkgS/J0vosngTaSoaG06WHi+xObQkaAdlbanP8B2+2l0f90LmUAAAAASUVORK5CYII=); }
+39
View File
@@ -0,0 +1,39 @@
/* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */
/*<!--activeline-->*/
.cm-s-midnight .CodeMirror-activeline-background { background: #253540; }
.cm-s-midnight.CodeMirror {
background: #0F192A;
color: #D1EDFF;
}
.cm-s-midnight div.CodeMirror-selected { background: #314D67; }
.cm-s-midnight .CodeMirror-line::selection, .cm-s-midnight .CodeMirror-line > span::selection, .cm-s-midnight .CodeMirror-line > span > span::selection { background: rgba(49, 77, 103, .99); }
.cm-s-midnight .CodeMirror-line::-moz-selection, .cm-s-midnight .CodeMirror-line > span::-moz-selection, .cm-s-midnight .CodeMirror-line > span > span::-moz-selection { background: rgba(49, 77, 103, .99); }
.cm-s-midnight .CodeMirror-gutters { background: #0F192A; border-right: 1px solid; }
.cm-s-midnight .CodeMirror-guttermarker { color: white; }
.cm-s-midnight .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
.cm-s-midnight .CodeMirror-linenumber { color: #D0D0D0; }
.cm-s-midnight .CodeMirror-cursor { border-left: 1px solid #F8F8F0; }
.cm-s-midnight span.cm-comment { color: #428BDD; }
.cm-s-midnight span.cm-atom { color: #AE81FF; }
.cm-s-midnight span.cm-number { color: #D1EDFF; }
.cm-s-midnight span.cm-property, .cm-s-midnight span.cm-attribute { color: #A6E22E; }
.cm-s-midnight span.cm-keyword { color: #E83737; }
.cm-s-midnight span.cm-string { color: #1DC116; }
.cm-s-midnight span.cm-variable { color: #FFAA3E; }
.cm-s-midnight span.cm-variable-2 { color: #FFAA3E; }
.cm-s-midnight span.cm-def { color: #4DD; }
.cm-s-midnight span.cm-bracket { color: #D1EDFF; }
.cm-s-midnight span.cm-tag { color: #449; }
.cm-s-midnight span.cm-link { color: #AE81FF; }
.cm-s-midnight span.cm-error { background: #F92672; color: #F8F8F0; }
.cm-s-midnight .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
+41
View File
@@ -0,0 +1,41 @@
/* Based on Sublime Text's Monokai theme */
.cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; }
.cm-s-monokai div.CodeMirror-selected { background: #49483E; }
.cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); }
.cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); }
.cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; }
.cm-s-monokai .CodeMirror-guttermarker { color: white; }
.cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
.cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; }
.cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
.cm-s-monokai span.cm-comment { color: #75715e; }
.cm-s-monokai span.cm-atom { color: #ae81ff; }
.cm-s-monokai span.cm-number { color: #ae81ff; }
.cm-s-monokai span.cm-comment.cm-attribute { color: #97b757; }
.cm-s-monokai span.cm-comment.cm-def { color: #bc9262; }
.cm-s-monokai span.cm-comment.cm-tag { color: #bc6283; }
.cm-s-monokai span.cm-comment.cm-type { color: #5998a6; }
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; }
.cm-s-monokai span.cm-keyword { color: #f92672; }
.cm-s-monokai span.cm-builtin { color: #66d9ef; }
.cm-s-monokai span.cm-string { color: #e6db74; }
.cm-s-monokai span.cm-variable { color: #f8f8f2; }
.cm-s-monokai span.cm-variable-2 { color: #9effff; }
.cm-s-monokai span.cm-variable-3, .cm-s-monokai span.cm-type { color: #66d9ef; }
.cm-s-monokai span.cm-def { color: #fd971f; }
.cm-s-monokai span.cm-bracket { color: #f8f8f2; }
.cm-s-monokai span.cm-tag { color: #f92672; }
.cm-s-monokai span.cm-header { color: #ae81ff; }
.cm-s-monokai span.cm-link { color: #ae81ff; }
.cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; }
.cm-s-monokai .CodeMirror-activeline-background { background: #373831; }
.cm-s-monokai .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
+143
View File
@@ -0,0 +1,143 @@
/*
Name: Moxer Theme
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://github.com/moxer-theme/moxer-code
*/
.cm-s-moxer.CodeMirror {
background-color: #090A0F;
color: #8E95B4;
line-height: 1.8;
}
.cm-s-moxer .CodeMirror-gutters {
background: #090A0F;
color: #35394B;
border: none;
}
.cm-s-moxer .CodeMirror-guttermarker,
.cm-s-moxer .CodeMirror-guttermarker-subtle,
.cm-s-moxer .CodeMirror-linenumber {
color: #35394B;
}
.cm-s-moxer .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-moxer div.CodeMirror-selected {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-moxer.CodeMirror-focused div.CodeMirror-selected {
background: #212431;
}
.cm-s-moxer .CodeMirror-line::selection,
.cm-s-moxer .CodeMirror-line>span::selection,
.cm-s-moxer .CodeMirror-line>span>span::selection {
background: #212431;
}
.cm-s-moxer .CodeMirror-line::-moz-selection,
.cm-s-moxer .CodeMirror-line>span::-moz-selection,
.cm-s-moxer .CodeMirror-line>span>span::-moz-selection {
background: #212431;
}
.cm-s-moxer .CodeMirror-activeline-background,
.cm-s-moxer .CodeMirror-activeline-gutter .CodeMirror-linenumber {
background: rgba(33, 36, 49, 0.5);
}
.cm-s-moxer .cm-keyword {
color: #D46C6C;
}
.cm-s-moxer .cm-operator {
color: #D46C6C;
}
.cm-s-moxer .cm-variable-2 {
color: #81C5DA;
}
.cm-s-moxer .cm-variable-3,
.cm-s-moxer .cm-type {
color: #f07178;
}
.cm-s-moxer .cm-builtin {
color: #FFCB6B;
}
.cm-s-moxer .cm-atom {
color: #A99BE2;
}
.cm-s-moxer .cm-number {
color: #7CA4C0;
}
.cm-s-moxer .cm-def {
color: #F5DFA5;
}
.cm-s-moxer .CodeMirror-line .cm-def ~ .cm-def {
color: #81C5DA;
}
.cm-s-moxer .cm-string {
color: #B2E4AE;
}
.cm-s-moxer .cm-string-2 {
color: #f07178;
}
.cm-s-moxer .cm-comment {
color: #3F445A;
}
.cm-s-moxer .cm-variable {
color: #8E95B4;
}
.cm-s-moxer .cm-tag {
color: #FF5370;
}
.cm-s-moxer .cm-meta {
color: #FFCB6B;
}
.cm-s-moxer .cm-attribute {
color: #C792EA;
}
.cm-s-moxer .cm-property {
color: #81C5DA;
}
.cm-s-moxer .cm-qualifier {
color: #DECB6B;
}
.cm-s-moxer .cm-variable-3,
.cm-s-moxer .cm-type {
color: #DECB6B;
}
.cm-s-moxer .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-moxer .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
+12
View File
@@ -0,0 +1,12 @@
.cm-s-neat span.cm-comment { color: #a86; }
.cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; }
.cm-s-neat span.cm-string { color: #a22; }
.cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; }
.cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; }
.cm-s-neat span.cm-variable { color: black; }
.cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; }
.cm-s-neat span.cm-meta { color: #555; }
.cm-s-neat span.cm-link { color: #3a3; }
.cm-s-neat .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-neat .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
+43
View File
@@ -0,0 +1,43 @@
/* neo theme for codemirror */
/* Color scheme */
.cm-s-neo.CodeMirror {
background-color:#ffffff;
color:#2e383c;
line-height:1.4375;
}
.cm-s-neo .cm-comment { color:#75787b; }
.cm-s-neo .cm-keyword, .cm-s-neo .cm-property { color:#1d75b3; }
.cm-s-neo .cm-atom,.cm-s-neo .cm-number { color:#75438a; }
.cm-s-neo .cm-node,.cm-s-neo .cm-tag { color:#9c3328; }
.cm-s-neo .cm-string { color:#b35e14; }
.cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier { color:#047d65; }
/* Editor styling */
.cm-s-neo pre {
padding:0;
}
.cm-s-neo .CodeMirror-gutters {
border:none;
border-right:10px solid transparent;
background-color:transparent;
}
.cm-s-neo .CodeMirror-linenumber {
padding:0;
color:#e0e2e5;
}
.cm-s-neo .CodeMirror-guttermarker { color: #1d75b3; }
.cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5; }
.cm-s-neo .CodeMirror-cursor {
width: auto;
border: 0;
background: rgba(155,157,162,0.37);
z-index: 1;
}
+27
View File
@@ -0,0 +1,27 @@
/* Loosely based on the Midnight Textmate theme */
.cm-s-night.CodeMirror { background: #0a001f; color: #f8f8f8; }
.cm-s-night div.CodeMirror-selected { background: #447; }
.cm-s-night .CodeMirror-line::selection, .cm-s-night .CodeMirror-line > span::selection, .cm-s-night .CodeMirror-line > span > span::selection { background: rgba(68, 68, 119, .99); }
.cm-s-night .CodeMirror-line::-moz-selection, .cm-s-night .CodeMirror-line > span::-moz-selection, .cm-s-night .CodeMirror-line > span > span::-moz-selection { background: rgba(68, 68, 119, .99); }
.cm-s-night .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; }
.cm-s-night .CodeMirror-guttermarker { color: white; }
.cm-s-night .CodeMirror-guttermarker-subtle { color: #bbb; }
.cm-s-night .CodeMirror-linenumber { color: #f8f8f8; }
.cm-s-night .CodeMirror-cursor { border-left: 1px solid white; }
.cm-s-night span.cm-comment { color: #8900d1; }
.cm-s-night span.cm-atom { color: #845dc4; }
.cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; }
.cm-s-night span.cm-keyword { color: #599eff; }
.cm-s-night span.cm-string { color: #37f14a; }
.cm-s-night span.cm-meta { color: #7678e2; }
.cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; }
.cm-s-night span.cm-variable-3, .cm-s-night span.cm-def, .cm-s-night span.cm-type { color: white; }
.cm-s-night span.cm-bracket { color: #8da6ce; }
.cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; }
.cm-s-night span.cm-link { color: #845dc4; }
.cm-s-night span.cm-error { color: #9d1e15; }
.cm-s-night .CodeMirror-activeline-background { background: #1C005A; }
.cm-s-night .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }
+42
View File
@@ -0,0 +1,42 @@
/* Based on arcticicestudio's Nord theme */
/* https://github.com/arcticicestudio/nord */
.cm-s-nord.CodeMirror { background: #2e3440; color: #d8dee9; }
.cm-s-nord div.CodeMirror-selected { background: #434c5e; }
.cm-s-nord .CodeMirror-line::selection, .cm-s-nord .CodeMirror-line > span::selection, .cm-s-nord .CodeMirror-line > span > span::selection { background: #3b4252; }
.cm-s-nord .CodeMirror-line::-moz-selection, .cm-s-nord .CodeMirror-line > span::-moz-selection, .cm-s-nord .CodeMirror-line > span > span::-moz-selection { background: #3b4252; }
.cm-s-nord .CodeMirror-gutters { background: #2e3440; border-right: 0px; }
.cm-s-nord .CodeMirror-guttermarker { color: #4c566a; }
.cm-s-nord .CodeMirror-guttermarker-subtle { color: #4c566a; }
.cm-s-nord .CodeMirror-linenumber { color: #4c566a; }
.cm-s-nord .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
.cm-s-nord span.cm-comment { color: #4c566a; }
.cm-s-nord span.cm-atom { color: #b48ead; }
.cm-s-nord span.cm-number { color: #b48ead; }
.cm-s-nord span.cm-comment.cm-attribute { color: #97b757; }
.cm-s-nord span.cm-comment.cm-def { color: #bc9262; }
.cm-s-nord span.cm-comment.cm-tag { color: #bc6283; }
.cm-s-nord span.cm-comment.cm-type { color: #5998a6; }
.cm-s-nord span.cm-property, .cm-s-nord span.cm-attribute { color: #8FBCBB; }
.cm-s-nord span.cm-keyword { color: #81A1C1; }
.cm-s-nord span.cm-builtin { color: #81A1C1; }
.cm-s-nord span.cm-string { color: #A3BE8C; }
.cm-s-nord span.cm-variable { color: #d8dee9; }
.cm-s-nord span.cm-variable-2 { color: #d8dee9; }
.cm-s-nord span.cm-variable-3, .cm-s-nord span.cm-type { color: #d8dee9; }
.cm-s-nord span.cm-def { color: #8FBCBB; }
.cm-s-nord span.cm-bracket { color: #81A1C1; }
.cm-s-nord span.cm-tag { color: #bf616a; }
.cm-s-nord span.cm-header { color: #b48ead; }
.cm-s-nord span.cm-link { color: #b48ead; }
.cm-s-nord span.cm-error { background: #bf616a; color: #f8f8f0; }
.cm-s-nord .CodeMirror-activeline-background { background: #3b4252; }
.cm-s-nord .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
+44
View File
@@ -0,0 +1,44 @@
/*
Name: oceanic-next
Author: Filype Pereira (https://github.com/fpereira1)
Original oceanic-next color scheme by Dmitri Voronianski (https://github.com/voronianski/oceanic-next-color-scheme)
*/
.cm-s-oceanic-next.CodeMirror { background: #304148; color: #f8f8f2; }
.cm-s-oceanic-next div.CodeMirror-selected { background: rgba(101, 115, 126, 0.33); }
.cm-s-oceanic-next .CodeMirror-line::selection, .cm-s-oceanic-next .CodeMirror-line > span::selection, .cm-s-oceanic-next .CodeMirror-line > span > span::selection { background: rgba(101, 115, 126, 0.33); }
.cm-s-oceanic-next .CodeMirror-line::-moz-selection, .cm-s-oceanic-next .CodeMirror-line > span::-moz-selection, .cm-s-oceanic-next .CodeMirror-line > span > span::-moz-selection { background: rgba(101, 115, 126, 0.33); }
.cm-s-oceanic-next .CodeMirror-gutters { background: #304148; border-right: 10px; }
.cm-s-oceanic-next .CodeMirror-guttermarker { color: white; }
.cm-s-oceanic-next .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
.cm-s-oceanic-next .CodeMirror-linenumber { color: #d0d0d0; }
.cm-s-oceanic-next .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
.cm-s-oceanic-next span.cm-comment { color: #65737E; }
.cm-s-oceanic-next span.cm-atom { color: #C594C5; }
.cm-s-oceanic-next span.cm-number { color: #F99157; }
.cm-s-oceanic-next span.cm-property { color: #99C794; }
.cm-s-oceanic-next span.cm-attribute,
.cm-s-oceanic-next span.cm-keyword { color: #C594C5; }
.cm-s-oceanic-next span.cm-builtin { color: #66d9ef; }
.cm-s-oceanic-next span.cm-string { color: #99C794; }
.cm-s-oceanic-next span.cm-variable,
.cm-s-oceanic-next span.cm-variable-2,
.cm-s-oceanic-next span.cm-variable-3 { color: #f8f8f2; }
.cm-s-oceanic-next span.cm-def { color: #6699CC; }
.cm-s-oceanic-next span.cm-bracket { color: #5FB3B3; }
.cm-s-oceanic-next span.cm-tag { color: #C594C5; }
.cm-s-oceanic-next span.cm-header { color: #C594C5; }
.cm-s-oceanic-next span.cm-link { color: #C594C5; }
.cm-s-oceanic-next span.cm-error { background: #C594C5; color: #f8f8f0; }
.cm-s-oceanic-next .CodeMirror-activeline-background { background: rgba(101, 115, 126, 0.33); }
.cm-s-oceanic-next .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
+85
View File
@@ -0,0 +1,85 @@
/*
Name: Panda Syntax
Author: Siamak Mokhtari (http://github.com/siamak/)
CodeMirror template by Siamak Mokhtari (https://github.com/siamak/atom-panda-syntax)
*/
.cm-s-panda-syntax {
background: #292A2B;
color: #E6E6E6;
line-height: 1.5;
font-family: 'Operator Mono', 'Source Code Pro', Menlo, Monaco, Consolas, Courier New, monospace;
}
.cm-s-panda-syntax .CodeMirror-cursor { border-color: #ff2c6d; }
.cm-s-panda-syntax .CodeMirror-activeline-background {
background: rgba(99, 123, 156, 0.1);
}
.cm-s-panda-syntax .CodeMirror-selected {
background: #FFF;
}
.cm-s-panda-syntax .cm-comment {
font-style: italic;
color: #676B79;
}
.cm-s-panda-syntax .cm-operator {
color: #f3f3f3;
}
.cm-s-panda-syntax .cm-string {
color: #19F9D8;
}
.cm-s-panda-syntax .cm-string-2 {
color: #FFB86C;
}
.cm-s-panda-syntax .cm-tag {
color: #ff2c6d;
}
.cm-s-panda-syntax .cm-meta {
color: #b084eb;
}
.cm-s-panda-syntax .cm-number {
color: #FFB86C;
}
.cm-s-panda-syntax .cm-atom {
color: #ff2c6d;
}
.cm-s-panda-syntax .cm-keyword {
color: #FF75B5;
}
.cm-s-panda-syntax .cm-variable {
color: #ffb86c;
}
.cm-s-panda-syntax .cm-variable-2 {
color: #ff9ac1;
}
.cm-s-panda-syntax .cm-variable-3, .cm-s-panda-syntax .cm-type {
color: #ff9ac1;
}
.cm-s-panda-syntax .cm-def {
color: #e6e6e6;
}
.cm-s-panda-syntax .cm-property {
color: #f3f3f3;
}
.cm-s-panda-syntax .cm-unit {
color: #ffb86c;
}
.cm-s-panda-syntax .cm-attribute {
color: #ffb86c;
}
.cm-s-panda-syntax .CodeMirror-matchingbracket {
border-bottom: 1px dotted #19F9D8;
padding-bottom: 2px;
color: #e6e6e6;
}
.cm-s-panda-syntax .CodeMirror-gutters {
background: #292a2b;
border-right-color: rgba(255, 255, 255, 0.1);
}
.cm-s-panda-syntax .CodeMirror-linenumber {
color: #e6e6e6;
opacity: 0.6;
}
+38
View File
@@ -0,0 +1,38 @@
/*
Name: Paraíso (Dark)
Author: Jan T. Sott
Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror)
Inspired by the art of Rubens LP (http://www.rubenslp.com.br)
*/
.cm-s-paraiso-dark.CodeMirror { background: #2f1e2e; color: #b9b6b0; }
.cm-s-paraiso-dark div.CodeMirror-selected { background: #41323f; }
.cm-s-paraiso-dark .CodeMirror-line::selection, .cm-s-paraiso-dark .CodeMirror-line > span::selection, .cm-s-paraiso-dark .CodeMirror-line > span > span::selection { background: rgba(65, 50, 63, .99); }
.cm-s-paraiso-dark .CodeMirror-line::-moz-selection, .cm-s-paraiso-dark .CodeMirror-line > span::-moz-selection, .cm-s-paraiso-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(65, 50, 63, .99); }
.cm-s-paraiso-dark .CodeMirror-gutters { background: #2f1e2e; border-right: 0px; }
.cm-s-paraiso-dark .CodeMirror-guttermarker { color: #ef6155; }
.cm-s-paraiso-dark .CodeMirror-guttermarker-subtle { color: #776e71; }
.cm-s-paraiso-dark .CodeMirror-linenumber { color: #776e71; }
.cm-s-paraiso-dark .CodeMirror-cursor { border-left: 1px solid #8d8687; }
.cm-s-paraiso-dark span.cm-comment { color: #e96ba8; }
.cm-s-paraiso-dark span.cm-atom { color: #815ba4; }
.cm-s-paraiso-dark span.cm-number { color: #815ba4; }
.cm-s-paraiso-dark span.cm-property, .cm-s-paraiso-dark span.cm-attribute { color: #48b685; }
.cm-s-paraiso-dark span.cm-keyword { color: #ef6155; }
.cm-s-paraiso-dark span.cm-string { color: #fec418; }
.cm-s-paraiso-dark span.cm-variable { color: #48b685; }
.cm-s-paraiso-dark span.cm-variable-2 { color: #06b6ef; }
.cm-s-paraiso-dark span.cm-def { color: #f99b15; }
.cm-s-paraiso-dark span.cm-bracket { color: #b9b6b0; }
.cm-s-paraiso-dark span.cm-tag { color: #ef6155; }
.cm-s-paraiso-dark span.cm-link { color: #815ba4; }
.cm-s-paraiso-dark span.cm-error { background: #ef6155; color: #8d8687; }
.cm-s-paraiso-dark .CodeMirror-activeline-background { background: #4D344A; }
.cm-s-paraiso-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }

Some files were not shown because too many files have changed in this diff Show More