diff --git a/app/build.gradle b/app/build.gradle
index 3d4e30e6a..acd7bf335 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -140,7 +140,6 @@ dependencies {
// UI libs
implementation 'com.github.Pixplicity:gene-rate:v1.1.8'
implementation 'com.github.AppIntro:AppIntro:6.2.0'
- implementation 'com.github.kailash09dabhi:OmRecorder:1.1.5'
implementation 'com.github.mertakdut:EpubParser:1.0.95'
implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d8ee6a8d6..28b5d48bc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,7 +19,6 @@
- _insertItem.callback(InsertType.LINK_SEARCH));
buttonPictureCamera.setOnClickListener(b -> _insertItem.callback(InsertType.IMAGE_CAMERA));
buttonPictureGallery.setOnClickListener(v -> _insertItem.callback(InsertType.IMAGE_GALLERY));
- buttonAudioRecord.setOnClickListener(v -> _insertItem.callback(InsertType.AUDIO_RECORDING));
buttonPictureEdit.setOnClickListener(v -> _insertItem.callback(InsertType.IMAGE_EDIT));
dialog.show();
@@ -368,7 +360,7 @@ private static void insertItem(
if (GsTextUtils.isNullOrEmpty(nameEdit.getText())) {
nameEdit.setText(pathEdit.getText());
}
- } else {
+ } else {
if (pathEdit != null) {
pathEdit.setText(GsFileUtils.relativePath(currentFile, file));
}
@@ -408,7 +400,7 @@ private static void insertItem(
}
case AUDIO_RECORDING: {
if (!cu.requestAudioRecording(activity, insertFileLink)) {
- GsAudioRecordOmDialog.showAudioRecordDialog(activity, R.string.record_audio, insertFileLink);
+ // noop, OM library is outdated and so voice recording feature removed
}
break;
}
diff --git a/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java b/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java
index d5ad5ab68..42dec9e89 100644
--- a/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java
+++ b/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java
@@ -169,7 +169,7 @@ private boolean runHighlight(final boolean recompute) {
private void updateHighlighting() {
if (runHighlight(false)) {
// Do not batch as we do not want to reflow
- _hl.clearDynamic().applyDynamic(hlRegion());
+ _hl.clearDynamic().applyDynamic(hlRegion());
_oldHlRect.set(_hlRect);
}
}
@@ -704,7 +704,12 @@ public void draw(final Canvas canvas) {
final int count = layout.getLineCount();
final int offsetY = _editor.getPaddingTop();
for (; i < count; i++) {
- final int start = layout.getLineStart(i);
+ int start;
+ try {
+ start = layout.getLineStart(i);
+ } catch (IndexOutOfBoundsException ex) {
+ break; // Even though the drawing is against count, might throw IndexOutOfBounds during drawing
+ }
if (start == 0 || text.charAt(start - 1) == '\n') {
final int y = layout.getLineBaseline(i);
if (y > _lineNumbersArea.bottom) {
diff --git a/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java b/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java
index 25de2206b..fdd5a36ba 100644
--- a/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java
+++ b/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java
@@ -375,7 +375,7 @@ public SyntaxHighlighterBase applyStatic() {
boolean hasStatic = false;
for (final SpanGroup group : _groups) {
- if (group.isStatic) {
+ if (group != null && group.isStatic) {
hasStatic = true;
_spannable.setSpan(group.span, group.start, group.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
diff --git a/app/src/main/java/net/gsantner/opoc/frontend/GsAudioRecordOmDialog.java b/app/src/main/java/net/gsantner/opoc/frontend/GsAudioRecordOmDialog.java
deleted file mode 100644
index 8f38b9bef..000000000
--- a/app/src/main/java/net/gsantner/opoc/frontend/GsAudioRecordOmDialog.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*#######################################################
- *
- * SPDX-FileCopyrightText: 2020-2024 Gregor Santner
- * SPDX-License-Identifier: Unlicense OR CC0-1.0
- *
- * Written 2020-2024 by Gregor Santner
- * To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
- * You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see .
-#########################################################*/
-package net.gsantner.opoc.frontend;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.pm.PackageManager;
-import android.graphics.Color;
-import android.media.AudioFormat;
-import android.media.MediaPlayer;
-import android.media.MediaRecorder;
-import android.os.Build;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.StringRes;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-
-import net.gsantner.opoc.util.GsFileUtils;
-import net.gsantner.opoc.wrapper.GsCallback;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import omrecorder.AudioRecordConfig;
-import omrecorder.OmRecorder;
-import omrecorder.PullTransport;
-import omrecorder.PullableSource;
-import omrecorder.Recorder;
-
-
-//
-// Callback: Called when successfully recorded
-// will contain path to file in cache directory. Must be copied to custom location in callback handler
-//
-// Add to build.gradle: implementation 'com.kailashdabhi:om-recorder:1.1.5'
-// Add to manifest:
-//
-@SuppressWarnings({"ResultOfMethodCallIgnored", "unused"})
-public class GsAudioRecordOmDialog {
- public static void showAudioRecordDialog(final Activity activity, @StringRes final int titleResId, final GsCallback.a1 recordFinishedCallbackWithPathToTemporaryFile) {
- ////////////////////////////////////
- // Request permission in case not granted. Do not show dialog UI in this case
- if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.RECORD_AUDIO}, 200);
- return;
- }
-
- ////////////////////////////////////
- // Init
- final String EMOJI_MICROPHONE = "\uD83D\uDD34";
- final String EMOJI_STOP = "⭕";//"\uD83D\uDED1";
- final String EMOJI_RESTART = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? "\uD83D\uDD04" : EMOJI_MICROPHONE;
- final String EMOJI_SPEAKER = "\uD83D\uDD0A"; //"\uD83C\uDFA7";
-
- final AtomicBoolean isRecording = new AtomicBoolean();
- final AtomicBoolean isRecordSavedOnce = new AtomicBoolean();
- final AtomicReference recorder = new AtomicReference<>();
- final AtomicReference mediaPlayer = new AtomicReference<>();
- final AtomicReference dialog = new AtomicReference<>();
- final AtomicReference startTime = new AtomicReference<>();
- final File TMP_FILE_RECORDING = generateFilename(activity.getCacheDir());
- if (TMP_FILE_RECORDING.exists()) {
- TMP_FILE_RECORDING.delete();
- }
-
- // Record management callbacks
- final GsCallback.a2 recorderManager = (cbArgRestart, cbArgStop) -> {
- if (cbArgRestart) {
- final PullableSource SRC_MICROPHONE = new PullableSource.Default(new AudioRecordConfig.Default(MediaRecorder.AudioSource.MIC, AudioFormat.ENCODING_PCM_16BIT, AudioFormat.CHANNEL_IN_STEREO, 44100));
- recorder.set(OmRecorder.wav(new PullTransport.Default(SRC_MICROPHONE), TMP_FILE_RECORDING));
- recorder.get().startRecording();
- startTime.set(System.currentTimeMillis());
- } else if (cbArgStop) {
- try {
- recorder.get().stopRecording();
- isRecordSavedOnce.set(true);
-
- int[] diff = GsFileUtils.getTimeDiffHMS(System.currentTimeMillis(), startTime.get());
- dialog.get().setMessage(String.format(Locale.getDefault(), "%02d:%02d:%02d / %s [.wav]", diff[0], diff[1], diff[2], GsFileUtils.getReadableFileSize(TMP_FILE_RECORDING.length(), true)));
- } catch (Exception ignored) {
- }
- }
- };
-
- ////////////////////////////////////
- // Create UI
- final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity);
- final LinearLayout layout = new LinearLayout(activity);
- layout.setOrientation(LinearLayout.HORIZONTAL);
- layout.setGravity(Gravity.CENTER_HORIZONTAL);
- final TextView playbackButton = new TextView(activity);
- final TextView recordButton = new TextView(activity);
- final View sep1 = new View(activity);
- sep1.setLayoutParams(new LinearLayout.LayoutParams(100, 1));
-
- // Record button
- recordButton.setTextColor(Color.BLACK);
- recordButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 64);
- recordButton.setGravity(Gravity.CENTER_HORIZONTAL);
- recordButton.setText(EMOJI_MICROPHONE);
- recordButton.setOnClickListener(v -> {
- if (isRecording.get()) {
- recorderManager.callback(false, true);
- } else {
- recorderManager.callback(true, false);
- }
-
- // Update state
- isRecording.set(!isRecording.get());
- recordButton.setText(isRecording.get() ? EMOJI_STOP : EMOJI_RESTART);
- playbackButton.setEnabled(!isRecording.get());
- });
-
- final GsCallback.a0 playbackStoppedCallback = () -> {
- recordButton.setEnabled(true);
- if (mediaPlayer.get() != null) {
- mediaPlayer.getAndSet(null).release();
- }
- playbackButton.setText(EMOJI_SPEAKER);
- };
-
- // Play button
- playbackButton.setTextColor(Color.BLACK);
- playbackButton.setEnabled(false);
- playbackButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 64);
- playbackButton.setGravity(Gravity.CENTER_HORIZONTAL);
- playbackButton.setText(EMOJI_SPEAKER);
- playbackButton.setOnClickListener(v -> {
- final boolean startPlaybackNow = mediaPlayer.get() == null;
- recordButton.setEnabled(false);
- playbackButton.setText(startPlaybackNow ? EMOJI_STOP : EMOJI_SPEAKER);
- if (startPlaybackNow) {
- try {
- MediaPlayer player = new MediaPlayer();
- mediaPlayer.set(player);
- player.setDataSource(TMP_FILE_RECORDING.getAbsolutePath());
- player.prepare();
- player.start();
- player.setOnCompletionListener(mp -> playbackStoppedCallback.callback());
- player.setLooping(false);
- } catch (IOException ignored) {
- }
- } else {
- mediaPlayer.get().stop();
- playbackStoppedCallback.callback();
- }
- });
-
- ////////////////////////////////////
- // Callback for OK & Cancel dialog button
- final DialogInterface.OnClickListener dialogOkAndCancelListener = (dialogInterface, dialogButtonCase) -> {
- final boolean isSavePressed = (dialogButtonCase == DialogInterface.BUTTON_POSITIVE);
- if (isRecording.get() || isRecordSavedOnce.get()) {
- try {
- recorder.get().stopRecording();
- } catch (Exception ignored) {
- }
- if (!isSavePressed) {
- if (TMP_FILE_RECORDING.exists()) {
- TMP_FILE_RECORDING.delete();
- }
- } else if (recordFinishedCallbackWithPathToTemporaryFile != null) {
- recordFinishedCallbackWithPathToTemporaryFile.callback(TMP_FILE_RECORDING.getAbsolutePath());
- }
- }
- dialogInterface.dismiss();
- };
-
- ////////////////////////////////////
- // Tooltip
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- playbackButton.setTooltipText("Play recording / Stop playback");
- recordButton.setTooltipText("Record Audio (Voice Note)");
- }
-
- ////////////////////////////////////
- // Add to layout
- layout.addView(playbackButton);
- layout.addView(sep1);
- layout.addView(recordButton);
-
- ////////////////////////////////////
- // Create & show dialog
- dialogBuilder
- .setTitle(titleResId)
- .setPositiveButton(android.R.string.ok, dialogOkAndCancelListener)
- .setNegativeButton(android.R.string.cancel, dialogOkAndCancelListener)
- .setMessage("00:00:00 / 0kB [.wav]")
- .setView(layout);
- dialog.set(dialogBuilder.create());
- Window w;
- dialog.get().show();
- if ((w = dialog.get().getWindow()) != null) {
- w.setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
- WindowManager.LayoutParams wlp = w.getAttributes();
- wlp.gravity = Gravity.BOTTOM;
- w.setAttributes(wlp);
- }
- }
-
- public static File generateFilename(final File recordDirectory) {
- final String datestr = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", Locale.ENGLISH).format(new Date());
- return new File(recordDirectory, datestr + "-record.wav");
- }
-}
diff --git a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java
index 8e6e00409..e665d0547 100644
--- a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java
+++ b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java
@@ -152,7 +152,9 @@ public Map getVirtualFolders() {
}
for (final File file : ContextCompat.getExternalFilesDirs(_context, null)) {
- //noinspection DataFlowIssue
+ if (file == null || file.getParentFile() == null) {
+ continue;
+ }
final File remap = new File(VIRTUAL_STORAGE_ROOT, "AppData (" + file.getParentFile().toString().replace("/", "-").substring(1) + ")");
map.put(remap, file);
}
@@ -615,6 +617,12 @@ public void onLayoutChange(View v, int l, int t, int r, int b, int ol, int ot, i
});
}
+ private void postScrollToAndFlash(final File file) {
+ if (_recyclerView != null && file != null) {
+ _recyclerView.post(() -> scrollToAndFlash(file));
+ }
+ }
+
/**
* Scroll to a file in current folder and flash
*
@@ -675,6 +683,11 @@ private void loadFolder(final File folder, final File show) {
// This function is not called on the main thread, so post to the UI thread
private synchronized void _loadFolder(final @NonNull File folder, final @Nullable File toShow) {
+
+ if (_recyclerView == null) {
+ return;
+ }
+
final boolean folderChanged = !folder.equals(_currentFolder);
final List newData = new ArrayList<>();
@@ -696,7 +709,6 @@ private synchronized void _loadFolder(final @NonNull File folder, final @Nullabl
newData.add(new File(folder, "0"));
}
-
if (folder.equals(VIRTUAL_STORAGE_RECENTS)) {
newData.addAll(_dopt.recentFiles);
} else if (folder.equals(VIRTUAL_STORAGE_POPULAR)) {
@@ -762,10 +774,10 @@ private synchronized void _loadFolder(final @NonNull File folder, final @Nullabl
_layoutManager.onRestoreInstanceState(_folderScrollMap.remove(_currentFolder));
}
- _recyclerView.post(() -> scrollToAndFlash(toShow));
+ postScrollToAndFlash(toShow);
});
} else {
- _recyclerView.post(() -> scrollToAndFlash(toShow));
+ postScrollToAndFlash(toShow);
}
if (_dopt.listener != null) {
@@ -773,7 +785,7 @@ private synchronized void _loadFolder(final @NonNull File folder, final @Nullabl
}
});
} else {
- _recyclerView.post(() -> scrollToAndFlash(toShow));
+ postScrollToAndFlash(toShow);
}
}
diff --git a/app/src/main/java/net/gsantner/opoc/frontend/textview/TextViewUndoRedo.java b/app/src/main/java/net/gsantner/opoc/frontend/textview/TextViewUndoRedo.java
index f2c9cfc86..9a4f6c254 100644
--- a/app/src/main/java/net/gsantner/opoc/frontend/textview/TextViewUndoRedo.java
+++ b/app/src/main/java/net/gsantner/opoc/frontend/textview/TextViewUndoRedo.java
@@ -148,7 +148,12 @@ public void undo() {
final int end = start + (edit.after != null ? edit.after.length() : 0);
mIsUndoOrRedo = true;
- text.replace(start, end, edit.before);
+ try {
+ text.replace(start, end, edit.before);
+ } catch (Exception ex){
+ // In case a undo would crash the app, don't do it instead
+ return;
+ }
mIsUndoOrRedo = false;
// This will get rid of underlines inserted when editor tries to come
diff --git a/app/src/main/res/layout/select_path_dialog.xml b/app/src/main/res/layout/select_path_dialog.xml
index 70830d0b9..ff66f295b 100644
--- a/app/src/main/res/layout/select_path_dialog.xml
+++ b/app/src/main/res/layout/select_path_dialog.xml
@@ -93,15 +93,6 @@
android:drawableLeft="@drawable/ic_crop_black_24dp"
android:visibility="gone"
android:text="@string/edit_picture" />
-
-
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 210913efc..774dc0568 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -38,7 +38,7 @@ work. If not, see .
Поделиться с…ПоделитьсяПоиск
- Показывает результат разметки
+ Рендерить разметкуРедакторПапкиФайлы
@@ -56,10 +56,10 @@ work. If not, see .
Отображать письмо слева направоОшибка: не получилось создать папку блокнотаПоследнее изменение: %s
- Был изменён
+ Посл. ред.Выбрать текущую папкуВыбрать
- Выбрать всё
+ Выбрать всеСоздатьВыбрать элементыОдин элемент выбран
@@ -69,7 +69,7 @@ work. If not, see .
ИмяURL/путьВставить ссылку
- Форматировать ссылку
+ Формат ссылкиВставить изображениеРазработчикиСохранить расположение
@@ -82,7 +82,7 @@ work. If not, see .
Показать сторонние лицензииИзменить язык этого приложения. Перезапустите приложение, чтобы изменения вступили в силуЯзык
- Необходимо разрешение для чтения и записи файлов
+ Нужно разрешение для чтения и записи файловОткрыть с помощьюСортировать поДата
@@ -105,7 +105,7 @@ work. If not, see .
ВажностьИсторияСинхронизация
- Искать / Новый
+ Поиск / ОсобыйСохранитьОбновитьОчистить
@@ -141,7 +141,7 @@ work. If not, see .
Подсветка синтаксисаДинамическая подсветкаЗадержка подсветки
- Задержка в мс перед обновлением подсветки синтаксиса. Меньше - быстрее обновление и больше расход энергии, а старые устройства - медленнее.
+ Задержка в мсек при подсветке синтаксиса. Чем она меньше, тем быстрее обновление и больше расход энергии. Старые устройства могут замедляться.Подсвечивать введённый текстДинамическая подсветка увеличивает производительность. Может вызвать артефакты рендеринга.Выбрать размер шрифта в редакторе
@@ -167,9 +167,9 @@ work. If not, see .
Подсвечивать пробелы в конце строк с двумя и более пробеламиИспользовать моноширный шрифт для кодаОтключить подсветку блоков кода
- Автообновление номеров упорядоченных списков
- Это может повлиять на производительность.
- Использовать различные типы шрифтов. Динамическое изменение типа шрифта влияет на производительность приложения.
+ Авто-перенумерация упорядоченных списков
+ Настройка может замедлить приложение.
+ Использовать разные шрифты. Динамическое изменение шрифта замедлит приложение.ОбщиеРесурсыМеждустрочный пробел в процентах
@@ -177,41 +177,46 @@ work. If not, see .
Перемещать выполненные задачи в указанный файл в той же папкеСортировать задачи в выбранном порядкеСпециальная клавиша
- AsciiDoc особое
+ Настройки AsciiDocЛистать вверх (Page Up)Листать вниз (Page Down)Начало строки (Home)Конец строки (End)Начало документаКонец документа
+ Строку вверх
+ Строку вниз
+ Новая строка
+ Выбрать эту строкуВыбрать всё (Ctrl+A)ТабуляцияПробел нулевой ширины
- Начать снизу
+ Длинный пробел ( )
+ Открывать внизуПосле загрузки ставить курсор в конец документа
- Символ неупорядоченного списка
- Какой символ использовать для обозначения неупорядоченных списков
+ Знак неупорядоченного списка
+ Знак для обозначения неупорядоченных списковДефис-минус (-)Звёздочка (*)Плюс (+)Markor может редактировать только локальные, автономно доступные документы и не поддерживает файловые менеджеры, которые не предоставляют полный путь к файлу.
- Файл или директория не существует и не может быть создан
+ Файл или папка не существует и не может быть созданаРазрешенияВернуть пути по умолчанию?
- Разрешение не выдано
- Примечание: приложения синхронизации могут размещать файлы в пользовательских папках. Откройте их, соответственно изменяя папку документов Маркора, или в другом менеджере файлов.
+ Нет разрешений
+ Прим.: приложения синхронизации могут размещать файлы в различных папках. Открывайте эти файлы, выбирая папку документов из Маркора или проводника.Выравнивание по вертикалиВыравнивание по горизонталиПо умолчаниюПо левому краюПо правому краюПо верхнему краю
- Снизу
+ По низуЦентрИнтервал между кнопками действийГоризонтальный интервал между кнопками действийЦентрировать текст
- Начинать редактирование текста по центру
+ Работать с текстом в центре экранаБуфер обменаВыберите существующий документВыбрать или создать документ
@@ -220,7 +225,7 @@ work. If not, see .
ПереводДополнительная информация и помощьСообщить об ошибках
- У вас есть вопрос или проблема?
+ Есть вопрос или замечание?Присоединиться к сообществуОставить отзыв, сообщить об ошибках или предложить идеи по улучшениюПеревести это приложение на другие языки
@@ -235,7 +240,7 @@ work. If not, see .
Отключить подчёркивание ошибокОставить подсказки клавиатуры, но выключить красное подчёркиваниеМестоположение
- Строк
+ СтрокиЗнаковСловПапка
@@ -257,12 +262,12 @@ work. If not, see .
Приложение календаря не установлено!Частые документыЦвет
- Палитра приложения
+ Основная палитраИзменяет фоновый и основной цвет текстового редактора. Приспосабливается к тёмной и светлой теме приложения. Цвет выделения текста остаётся таким же.ШаблонПередний планФон
- Установить цвет текста или фона? Также возможна вставка hexcode #цвета.
+ Выбрать цвет текста или фона? Можно использовать также цветовой hex-код.ТипПапка— Папка создаётся без расширения
@@ -272,8 +277,9 @@ work. If not, see .
Открывать документы в отдельных окнах. Переключайтесь между ними с помощью кнопки «Обзор» на устройствеВведите или выберите форматВыбрать дату
- Выбор времени
+ Выбрать времяПолучить формат вместо даты или времени
+ Вставить дату и времяРезультатТолько времяТолько дата
@@ -453,4 +459,9 @@ work. If not, see .
Файлы содержащие менее %d символов не сохраняются автоматически во избежание случайного удаления данных.Расширенный синтаксис фильтрации]]>Фильтр
+ Регистр
+ Переключать регистр (напр., Слово->СЛОВО->слово)
+ Чередовать регистр (напр., СлОвО->сЛоВо)
+ Слова с заглавной (напр., слово->Слово)
+ Предложения с заглавных (напр., слово->Слово)