The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .editorconfig
├── .gitattributes
├── .github
    ├── ISSUE_TEMPLATE
    │   ├── bug_report.md
    │   ├── config.yml
    │   ├── feature_request.md
    │   └── question.md
    ├── PULL_REQUEST_TEMPLATE.md
    └── workflows
    │   ├── build.yml
    │   └── release.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app-framework
    ├── app-framework.gradle
    ├── app-framework.modularity
    └── src
    │   └── main
    │       ├── java
    │           ├── com
    │           │   └── unclezs
    │           │   │   └── novel
    │           │   │       └── app
    │           │   │           └── framework
    │           │   │               ├── animation
    │           │   │                   ├── CenterScaleTransition.java
    │           │   │                   ├── RightTransition.java
    │           │   │                   ├── ScaleLargeTransition.java
    │           │   │                   └── SmoothScrollingTransition.java
    │           │   │               ├── annotation
    │           │   │                   └── FxView.java
    │           │   │               ├── appication
    │           │   │                   ├── BaseApplication.java
    │           │   │                   ├── SceneNavigateBundle.java
    │           │   │                   └── SceneView.java
    │           │   │               ├── bundle
    │           │   │                   └── BaseBundle.java
    │           │   │               ├── collection
    │           │   │                   └── SimpleObservableList.java
    │           │   │               ├── components
    │           │   │                   ├── FileSelector.java
    │           │   │                   ├── ImageViewPlus.java
    │           │   │                   ├── InputBox.java
    │           │   │                   ├── Loading.java
    │           │   │                   ├── LoadingImageView.java
    │           │   │                   ├── ModalBox.java
    │           │   │                   ├── NumberTextField.java
    │           │   │                   ├── PlaceHolder.java
    │           │   │                   ├── SearchBar.java
    │           │   │                   ├── SelectableButton.java
    │           │   │                   ├── StageDecorator.java
    │           │   │                   ├── TabButton.java
    │           │   │                   ├── TabGroup.java
    │           │   │                   ├── Tag.java
    │           │   │                   ├── TitleBar.java
    │           │   │                   ├── Toast.java
    │           │   │                   ├── cell
    │           │   │                   │   └── BaseListCell.java
    │           │   │                   ├── icon
    │           │   │                   │   ├── Icon.java
    │           │   │                   │   ├── IconButton.java
    │           │   │                   │   └── IconFont.java
    │           │   │                   ├── properties
    │           │   │                   │   └── Str.java
    │           │   │                   └── sidebar
    │           │   │                   │   ├── SidebarMenu.java
    │           │   │                   │   ├── SidebarMenuItem.java
    │           │   │                   │   ├── SidebarNavigateBundle.java
    │           │   │                   │   ├── SidebarNavigation.java
    │           │   │                   │   └── SidebarView.java
    │           │   │               ├── core
    │           │   │                   ├── AppContext.java
    │           │   │                   ├── ContextDestroyedListener.java
    │           │   │                   ├── View.java
    │           │   │                   └── ViewFactory.java
    │           │   │               ├── exception
    │           │   │                   ├── BaseRuntimeException.java
    │           │   │                   ├── FxException.java
    │           │   │                   ├── IoRuntimeException.java
    │           │   │                   ├── ReflectionException.java
    │           │   │                   └── ResourceNotFoundException.java
    │           │   │               ├── executor
    │           │   │                   ├── DebounceTask.java
    │           │   │                   ├── Executor.java
    │           │   │                   ├── FluentTask.java
    │           │   │                   └── TaskFactory.java
    │           │   │               ├── serialize
    │           │   │                   ├── ListPropertyTypeAdapter.java
    │           │   │                   ├── PropertyJsonSerializer.java
    │           │   │                   └── ValuePropertyTypeAdapter.java
    │           │   │               ├── support
    │           │   │                   ├── LocalizedSupport.java
    │           │   │                   ├── fonts
    │           │   │                   │   └── FontsLoader.java
    │           │   │                   └── hotkey
    │           │   │                   │   ├── HotKeyCombination.java
    │           │   │                   │   ├── HotKeyManager.java
    │           │   │                   │   └── KeyRecorder.java
    │           │   │               └── util
    │           │   │                   ├── BindUtils.java
    │           │   │                   ├── Choosers.java
    │           │   │                   ├── ColorUtil.java
    │           │   │                   ├── DesktopUtils.java
    │           │   │                   ├── EventUtils.java
    │           │   │                   ├── FontUtils.java
    │           │   │                   ├── ImageLoader.java
    │           │   │                   ├── NodeHelper.java
    │           │   │                   ├── PlatformUtils.java
    │           │   │                   ├── ProxyUtils.java
    │           │   │                   ├── ReflectUtils.java
    │           │   │                   ├── ResourceUtils.java
    │           │   │                   ├── StrUtils.java
    │           │   │                   └── ToolTipUtil.java
    │           └── module-info.java
    │       └── resources
    │           ├── css
    │               ├── application.css
    │               ├── application.scss
    │               ├── define
    │               │   ├── _colors.scss
    │               │   ├── _global.scss
    │               │   └── _variables.scss
    │               └── widgets
    │               │   ├── _check-bok.scss
    │               │   ├── _combo-box.scss
    │               │   ├── _context-menu.scss
    │               │   ├── _hyperlink.scss
    │               │   ├── _input-box.scss
    │               │   ├── _list-view.scss
    │               │   ├── _loading-image-view.scss
    │               │   ├── _loading.scss
    │               │   ├── _modal.scss
    │               │   ├── _popup.scss
    │               │   ├── _progress-bar.scss
    │               │   ├── _scroll-pane.scss
    │               │   ├── _search-bar.scss
    │               │   ├── _sidebar-navigation.scss
    │               │   ├── _slider.scss
    │               │   ├── _spinner.scss
    │               │   ├── _stage-decorator.scss
    │               │   ├── _tab-button.scss
    │               │   ├── _tab-pane.scss
    │               │   ├── _table-view.scss
    │               │   ├── _tag.scss
    │               │   ├── _text-field.scss
    │               │   ├── _title-bar.scss
    │               │   ├── _toast.scss
    │               │   ├── _tooltip.scss
    │               │   ├── jfonix.css
    │               │   └── jfonix.scss
    │           ├── fonts
    │               └── iconfont.ttf
    │           └── images
    │               └── loading.gif
├── app-localized
    ├── app-localized.gradle
    └── src
    │   └── main
    │       └── resources
    │           └── com
    │               └── unclezs
    │                   └── novel
    │                       └── app
    │                           └── localized
    │                               ├── app.properties
    │                               ├── app
    │                                   └── home.properties
    │                               ├── app_en.properties
    │                               ├── app_zh_TW.properties
    │                               └── widgets
    │                                   ├── common.properties
    │                                   ├── common_en.properties
    │                                   ├── common_zh_TW.properties
    │                                   ├── modal.properties
    │                                   ├── modal_en.properties
    │                                   ├── modal_zh_TW.properties
    │                                   ├── stage-decorator.properties
    │                                   ├── stage-decorator_en.properties
    │                                   └── stage-decorator_zh_TW.properties
├── app
    ├── app.gradle
    ├── app.modularity
    ├── db
    │   ├── core.db
    │   └── init.sql
    ├── packager
    │   ├── bin
    │   │   ├── linux
    │   │   │   └── kindlegen_linux
    │   │   ├── mac
    │   │   │   └── kindlegen
    │   │   └── win
    │   │   │   └── kindlegen.exe
    │   ├── fonts
    │   │   └── pingfang-simple-bold.ttf
    │   ├── icon
    │   │   ├── favicon.icns
    │   │   ├── favicon.ico
    │   │   └── favicon.png
    │   ├── inno-setup
    │   │   └── language
    │   │   │   ├── chinese-simple.isl
    │   │   │   └── chinese-traditional.isl
    │   ├── resource
    │   │   └── conf
    │   │   │   ├── core.db
    │   │   │   ├── jul.properties
    │   │   │   └── launcher.vmoptions
    │   ├── sciprt
    │   │   └── run.bat
    │   └── screenshot
    │   │   ├── home.png
    │   │   ├── read.png
    │   │   ├── read1.png
    │   │   └── setting.png
    ├── proguard.pro
    └── src
    │   └── main
    │       ├── java
    │           ├── com
    │           │   └── unclezs
    │           │   │   └── novel
    │           │   │       └── app
    │           │   │           └── main
    │           │   │               ├── App.java
    │           │   │               ├── core
    │           │   │                   ├── ChapterComparator.java
    │           │   │                   ├── NovelSearcher.java
    │           │   │                   ├── loader
    │           │   │                   │   ├── AbstractBookLoader.java
    │           │   │                   │   ├── BookLoader.java
    │           │   │                   │   └── TxtLoader.java
    │           │   │                   ├── pipeline
    │           │   │                   │   └── EbookPipeline.java
    │           │   │                   ├── spi
    │           │   │                   │   └── WebEngineHttpClient.java
    │           │   │                   ├── spider
    │           │   │                   │   └── SpiderWrapper.java
    │           │   │                   └── webdav
    │           │   │                   │   └── WebDav.java
    │           │   │               ├── db
    │           │   │                   ├── BaseDao.java
    │           │   │                   ├── DataHelper.java
    │           │   │                   ├── beans
    │           │   │                   │   ├── AudioBook.java
    │           │   │                   │   ├── Book.java
    │           │   │                   │   ├── DownloadHistory.java
    │           │   │                   │   ├── SearchEngine.java
    │           │   │                   │   └── TxtTocRule.java
    │           │   │                   └── dao
    │           │   │                   │   ├── AudioBookDao.java
    │           │   │                   │   ├── BookDao.java
    │           │   │                   │   ├── DownloadHistoryDao.java
    │           │   │                   │   ├── SearchEngineDao.java
    │           │   │                   │   └── TxtTocRuleDao.java
    │           │   │               ├── enums
    │           │   │                   ├── DownloadType.java
    │           │   │                   └── SearchType.java
    │           │   │               ├── exception
    │           │   │                   └── DbException.java
    │           │   │               ├── manager
    │           │   │                   ├── HotkeyManager.java
    │           │   │                   ├── LanguageManager.java
    │           │   │                   ├── ResourceManager.java
    │           │   │                   ├── RuleManager.java
    │           │   │                   ├── SettingManager.java
    │           │   │                   └── TrayManager.java
    │           │   │               ├── model
    │           │   │                   ├── BookBundle.java
    │           │   │                   ├── BookCache.java
    │           │   │                   ├── ChapterProperty.java
    │           │   │                   └── config
    │           │   │                   │   ├── BackupConfig.java
    │           │   │                   │   ├── BasicConfig.java
    │           │   │                   │   ├── BookShelfConfig.java
    │           │   │                   │   ├── DownloadConfig.java
    │           │   │                   │   ├── HotKeyConfig.java
    │           │   │                   │   ├── ProxyConfig.java
    │           │   │                   │   ├── ReaderConfig.java
    │           │   │                   │   └── TTSConfig.java
    │           │   │               ├── test
    │           │   │                   ├── DebounceTaskTest.java
    │           │   │                   ├── DynamicPageTest.java
    │           │   │                   └── TTSTest.java
    │           │   │               ├── util
    │           │   │                   ├── BookHelper.java
    │           │   │                   ├── DbHelper.java
    │           │   │                   ├── DebugUtils.java
    │           │   │                   ├── EbookUtils.java
    │           │   │                   ├── EncodingDetect.java
    │           │   │                   ├── MixPanelHelper.java
    │           │   │                   ├── TimeUtil.java
    │           │   │                   ├── UpdateUtils.java
    │           │   │                   └── VelocityUtils.java
    │           │   │               ├── viewmodel
    │           │   │                   └── BackupViewModel.java
    │           │   │               └── views
    │           │   │                   ├── animation
    │           │   │                       ├── NextPageTransition.java
    │           │   │                       └── PrePageTransition.java
    │           │   │                   ├── components
    │           │   │                       ├── BookDetailModal.java
    │           │   │                       ├── BookNode.java
    │           │   │                       ├── cell
    │           │   │                       │   ├── ActionButtonTableCell.java
    │           │   │                       │   ├── AudioBookListCell.java
    │           │   │                       │   ├── BookListCell.java
    │           │   │                       │   ├── ChapterListCell.java
    │           │   │                       │   ├── CheckBoxTableCell.java
    │           │   │                       │   ├── DownloadActionTableCell.java
    │           │   │                       │   ├── DownloadHistoryActionTableCell.java
    │           │   │                       │   ├── ProgressBarTableCell.java
    │           │   │                       │   ├── TagTableCell.java
    │           │   │                       │   ├── TagsTableCell.java
    │           │   │                       │   └── TocListCell.java
    │           │   │                       ├── rule
    │           │   │                       │   ├── CommonRuleEditor.java
    │           │   │                       │   ├── ParamsEditor.java
    │           │   │                       │   ├── RuleItem.java
    │           │   │                       │   ├── RuleItems.java
    │           │   │                       │   └── ScriptDebugBox.java
    │           │   │                       └── setting
    │           │   │                       │   ├── BackupSettingView.java
    │           │   │                       │   ├── ProxySetting.java
    │           │   │                       │   ├── SearchEngineEditor.java
    │           │   │                       │   ├── SearchEngineSetting.java
    │           │   │                       │   ├── SettingItem.java
    │           │   │                       │   └── SettingItems.java
    │           │   │                   ├── home
    │           │   │                       ├── AnalysisView.java
    │           │   │                       ├── AudioBookShelfView.java
    │           │   │                       ├── DownloadManagerView.java
    │           │   │                       ├── FictionBookshelfView.java
    │           │   │                       ├── HeaderMenuView.java
    │           │   │                       ├── HomeView.java
    │           │   │                       ├── ImportBookView.java
    │           │   │                       ├── RuleEditorView.java
    │           │   │                       ├── RuleManagerView.java
    │           │   │                       ├── SearchAudioView.java
    │           │   │                       ├── SearchNetworkView.java
    │           │   │                       ├── SearchNovelView.java
    │           │   │                       ├── SettingView.java
    │           │   │                       ├── TestView.java
    │           │   │                       └── ThemeView.java
    │           │   │                   └── reader
    │           │   │                       ├── PageView.java
    │           │   │                       ├── ReaderThemeView.java
    │           │   │                       ├── ReaderView.java
    │           │   │                       ├── player
    │           │   │                           └── TTSPlayer.java
    │           │   │                       └── widgets
    │           │   │                           └── ReaderContextMenu.java
    │           └── module-info.java
    │       └── resources
    │           ├── assets
    │               ├── defaults
    │               │   ├── toc-rule.json
    │               │   └── tts.json
    │               ├── images
    │               │   ├── no-cover.jpg
    │               │   ├── plus.png
    │               │   └── reward.jpg
    │               └── logo
    │               │   ├── icon-128.png
    │               │   ├── icon-16.png
    │               │   ├── icon-32.png
    │               │   ├── icon-48.png
    │               │   └── icon-64.png
    │           ├── css
    │               ├── define
    │               │   ├── _colors.scss
    │               │   ├── _global.scss
    │               │   └── _variables.scss
    │               ├── home
    │               │   ├── home.css
    │               │   ├── home.scss
    │               │   ├── theme
    │               │   │   ├── _theme.scss
    │               │   │   ├── blue.css
    │               │   │   ├── blue.scss
    │               │   │   ├── dark.css
    │               │   │   ├── dark.scss
    │               │   │   ├── deep-blue.css
    │               │   │   ├── deep-blue.scss
    │               │   │   ├── default.css
    │               │   │   ├── default.scss
    │               │   │   ├── green.css
    │               │   │   ├── green.scss
    │               │   │   ├── grey.css
    │               │   │   ├── grey.scss
    │               │   │   ├── image.css
    │               │   │   ├── image.scss
    │               │   │   ├── light-blue.css
    │               │   │   ├── light-blue.scss
    │               │   │   ├── orange.css
    │               │   │   ├── orange.scss
    │               │   │   ├── pink.css
    │               │   │   ├── pink.scss
    │               │   │   ├── purple.css
    │               │   │   ├── purple.scss
    │               │   │   ├── red.css
    │               │   │   ├── red.scss
    │               │   │   ├── teal.css
    │               │   │   ├── teal.scss
    │               │   │   ├── yellow.css
    │               │   │   └── yellow.scss
    │               │   ├── views
    │               │   │   ├── _analysis-download.scss
    │               │   │   ├── _audio-bookshelf.scss
    │               │   │   ├── _download-manager.scss
    │               │   │   ├── _fiction-bookshelf.scss
    │               │   │   ├── _header-menu.scss
    │               │   │   ├── _import-book.scss
    │               │   │   ├── _rule-editor.scss
    │               │   │   ├── _rule-manager.scss
    │               │   │   ├── _search-audio.scss
    │               │   │   ├── _search-network.scss
    │               │   │   ├── _search-novel.scss
    │               │   │   ├── _setting.scss
    │               │   │   ├── _theme.scss
    │               │   │   └── widgets
    │               │   │   │   ├── _book-detail.scss
    │               │   │   │   └── _search-engine-editor.scss
    │               │   └── webview
    │               │   │   ├── baidu.css
    │               │   │   ├── baidu.scss
    │               │   │   ├── bing.css
    │               │   │   ├── bing.scss
    │               │   │   ├── google.css
    │               │   │   └── google.scss
    │               └── reader
    │               │   ├── reader.css
    │               │   ├── reader.scss
    │               │   ├── theme
    │               │       ├── _theme.scss
    │               │       ├── custom.css
    │               │       ├── custom.scss
    │               │       ├── darcula.css
    │               │       ├── darcula.scss
    │               │       ├── dark.css
    │               │       ├── dark.scss
    │               │       ├── default.css
    │               │       ├── default.scss
    │               │       ├── green.css
    │               │       ├── green.scss
    │               │       ├── grey.css
    │               │       ├── grey.scss
    │               │       ├── pink.css
    │               │       ├── pink.scss
    │               │       ├── teal.css
    │               │       ├── teal.scss
    │               │       ├── white.css
    │               │       ├── white.scss
    │               │       ├── yellow.css
    │               │       └── yellow.scss
    │               │   └── widgets
    │               │       └── _theme-view.scss
    │           ├── layout
    │               ├── home
    │               │   ├── analysis.fxml
    │               │   ├── audio-bookshelf.fxml
    │               │   ├── download-manager.fxml
    │               │   ├── fiction-bookshelf.fxml
    │               │   ├── header-menu.fxml
    │               │   ├── home.fxml
    │               │   ├── import-book.fxml
    │               │   ├── rule-editor.fxml
    │               │   ├── rule-manager.fxml
    │               │   ├── search-audio.fxml
    │               │   ├── search-network.fxml
    │               │   ├── search-novel.fxml
    │               │   ├── setting.fxml
    │               │   ├── test.fxml
    │               │   └── theme.fxml
    │               └── reader
    │               │   ├── reader.fxml
    │               │   └── setting.fxml
    │           ├── logback.xml
    │           └── templates
    │               └── epub
    │                   ├── META-INF
    │                       └── container.xml
    │                   ├── content.opf.vm
    │                   ├── mimetype
    │                   ├── style
    │                       └── style.css
    │                   ├── text
    │                       ├── chapter.html.vm
    │                       └── toc.html.vm
    │                   ├── toc.ncx.vm
    │                   └── velocity_implicit.vm
├── build.gradle
├── buildSrc
    ├── build.gradle
    ├── settings.gradle
    └── src
    │   └── main
    │       ├── java
    │           ├── com
    │           │   └── unclezs
    │           │   │   └── novel
    │           │   │       └── app
    │           │   │           ├── icon
    │           │   │               └── IconTask.java
    │           │   │           ├── localized
    │           │   │               └── TranslateTask.java
    │           │   │           └── packager
    │           │   │               ├── Context.java
    │           │   │               ├── LauncherHelper.java
    │           │   │               ├── PackagePlugin.java
    │           │   │               ├── exception
    │           │   │                   └── PackageException.java
    │           │   │               ├── model
    │           │   │                   ├── FxPlatform.java
    │           │   │                   ├── LauncherConfig.java
    │           │   │                   ├── LinuxConfig.java
    │           │   │                   ├── MacConfig.java
    │           │   │                   ├── PackagerExtension.java
    │           │   │                   ├── Platform.java
    │           │   │                   ├── PlatformConfig.java
    │           │   │                   ├── WinConfig.java
    │           │   │                   └── WindowsSigning.java
    │           │   │               ├── package-info.java
    │           │   │               ├── packager
    │           │   │                   ├── AbstractPackager.java
    │           │   │                   ├── LinuxPackager.java
    │           │   │                   ├── MacPackager.java
    │           │   │                   └── WindowsPackager.java
    │           │   │               ├── subtask
    │           │   │                   ├── BaseSubTask.java
    │           │   │                   ├── CopyDependencies.java
    │           │   │                   ├── CreateCompressedPackage.java
    │           │   │                   ├── CreateJre.java
    │           │   │                   ├── CreateLauncher.java
    │           │   │                   ├── CreateRunnableJar.java
    │           │   │                   ├── linux
    │           │   │                   │   ├── GenerateDeb.java
    │           │   │                   │   └── GenerateRpm.java
    │           │   │                   ├── mac
    │           │   │                   │   ├── GenerateDmg.java
    │           │   │                   │   └── GeneratePkg.java
    │           │   │                   └── windows
    │           │   │                   │   ├── CreateExe.java
    │           │   │                   │   ├── GenerateMsi.java
    │           │   │                   │   ├── GenerateMsm.java
    │           │   │                   │   ├── GenerateSetup.java
    │           │   │                   │   └── WinSubTask.java
    │           │   │               ├── task
    │           │   │                   ├── PackageTask.java
    │           │   │                   └── UpgradeTask.java
    │           │   │               └── util
    │           │   │                   ├── ExecUtils.java
    │           │   │                   ├── FileUtils.java
    │           │   │                   ├── JdkUtils.java
    │           │   │                   ├── Logger.java
    │           │   │                   ├── SignerHelper.java
    │           │   │                   └── VelocityUtils.java
    │           └── org
    │           │   └── openjfx
    │           │       └── gradle
    │           │           ├── JavaFXModule.java
    │           │           ├── JavaFXOptions.java
    │           │           ├── JavaFXPlatform.java
    │           │           ├── JavaFXPlugin.java
    │           │           └── tasks
    │           │               └── ExecTask.java
    │       └── resources
    │           ├── icon
    │               └── IconFont.java.vm
    │           ├── packager
    │               ├── linux
    │               │   ├── control.vm
    │               │   ├── default-icon.png
    │               │   ├── default-icon.xpm
    │               │   ├── desktop.vm
    │               │   └── startup.sh.vm
    │               ├── mac
    │               │   ├── Info.plist.vm
    │               │   ├── background.png
    │               │   ├── customize-dmg.applescript.vm
    │               │   ├── default-icon.icns
    │               │   ├── startup.vm
    │               │   └── universalJavaApplicationStub
    │               └── windows
    │               │   ├── default-icon.ico
    │               │   ├── exe4j.vm
    │               │   ├── iss.vm
    │               │   ├── msm.wxs.vm
    │               │   └── wxs.vm
    │           └── velocity_implicit.vm
├── gradle.properties
├── gradle
    ├── dependency-management.gradle
    ├── publications.gradle
    └── wrapper
    │   ├── gradle-wrapper.jar
    │   └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle


/.editorconfig:
--------------------------------------------------------------------------------
 1 | root = true
 2 | 
 3 | [{*.java, *.fxml}]
 4 | indent_style = space
 5 | indent_size = 2
 6 | end_of_line = lf
 7 | charset = utf-8
 8 | trim_trailing_whitespace = true
 9 | insert_final_newline = true
10 | 


--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
 1 | *       text eof=lf
 2 | *.bat   -text
 3 | *.gif   binary
 4 | *.jar   binary
 5 | *.jpeg  binary
 6 | *.jpg   binary
 7 | *.png   binary
 8 | *.vsd   binary
 9 | *.ttf   binary
10 | *.exe   binary
11 | kindle*   binary
12 | exe.vsd   binary
13 | *.icns binary
14 | *.db binary


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: 提交BUG
 3 | about: 提交BUG及问题
 4 | title: ''
 5 | labels: bug
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | <!--
11 |   感谢您花时间创建一个issue,请参照一下模板进行问题描述:
12 | -->
13 | 
14 | **问题简述**
15 | 
16 | 阐述 记录/问题/事件/... 发生的背景
17 | 
18 | **复现步骤**
19 | 
20 | 如何操作才能够复现
21 | 
22 | **自我分析**
23 | 
24 | 先给出自己的态度以及尝试
25 | 
26 | **截图与日志**
27 | 
28 | 错误的截图和日志
29 | 
30 | **操作环境**
31 | 
32 | 系统配置及版本信息等
33 | 
34 | **其他补充**
35 | 
36 | 其他内容补充
37 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: 新增功能
 3 | about: 如果你有想增加的功能
 4 | title: ''
 5 | labels: enhancement
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **功能描述**
11 | 
12 | 描述下你想要的功能
13 | 
14 | **应用场景**
15 | 
16 | 什么场景下能够用到这个功能
17 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: 需求帮助
 3 | about: 提出使用中的一些疑问
 4 | title: ''
 5 | labels: question
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **疑问描述**
11 | 
12 | 描述下你的问题
13 | 


--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | <!--- 感谢你为Uncle小说做出的贡献! :-) -->
2 | 
3 | ### 本次PR的主要内容
4 | 


--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
 1 | name: test build
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ main ]
 6 |   pull_request:
 7 |     branches: [ main ]
 8 | 
 9 | jobs:
10 |   build:
11 |     runs-on: ubuntu-latest
12 |     strategy:
13 |       matrix:
14 |         java: [11, 17]
15 |     steps:
16 |     - uses: actions/checkout@v2
17 |     - name: Set up JDK ${{ matrix.java }}
18 |       uses: actions/setup-java@v3
19 |       with:
20 |         java-version: ${{ matrix.java }}
21 |         distribution: 'zulu'
22 |     - name: Build with Gradle
23 |       run: ./gradlew build -x test
24 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | target
 2 | /*.iml
 3 | logs
 4 | *.tmp
 5 | *.DS_Store
 6 | lib/
 7 | downloads/
 8 | *.txt
 9 | build/
10 | .gradle/
11 | out/
12 | tmp/
13 | .idea
14 | /conf/
15 | /bin/
16 | /caches/
17 | /backup/
18 | /fonts/
19 | /.run/
20 | changelog-latest.md


--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "packager"]
2 | 	path = packager
3 | 	url = https://github.com/uncle-novel/packager
4 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright <2021> <unclezs>
2 | 
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 | 
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 | 
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 | 
9 | 


--------------------------------------------------------------------------------
/app-framework/app-framework.gradle:
--------------------------------------------------------------------------------
 1 | import com.unclezs.novel.app.icon.IconTask
 2 | 
 3 | plugins {
 4 |     id "java-library"
 5 | }
 6 | dependencies {
 7 |     implementation project(":app-localized")
 8 |     api "com.github.tulskiy:jkeymaster"
 9 |     api "cn.hutool:hutool-core"
10 |     api "cn.hutool:hutool-cache"
11 |     api "org.rationalityfrontline.workaround:jfoenix"
12 |     api "org.slf4j:slf4j-api"
13 |     api 'com.unclezs:novel-analyzer'
14 | }
15 | jar {
16 |     manifest {
17 |     }
18 | }
19 | // 生成字体图标
20 | task generateIcon(type: IconTask, group: "common") {
21 |     url = "https://at.alicdn.com/t/font_1469921_exy14bhfli.json"
22 |     out = "${sourceSets.main.java.srcDirs[0]}"
23 |     outFont = "${sourceSets.main.resources.srcDirs[0]}/fonts/iconfont.ttf"
24 |     packageName = "com.unclezs.novel.app.framework.components.icon"
25 |     className = "IconFont"
26 | }


--------------------------------------------------------------------------------
/app-framework/app-framework.modularity:
--------------------------------------------------------------------------------
1 | --add-exports=javafx.graphics/com.sun.javafx.css=com.unclezs.novel.app.framework
2 | --add-exports=javafx.graphics/com.sun.javafx.stage=com.unclezs.novel.app.framework
3 | --add-exports=javafx.base/com.sun.javafx.collections=com.unclezs.novel.app.framework


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/animation/CenterScaleTransition.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.animation;
 2 | 
 3 | import com.jfoenix.transitions.CachedTransition;
 4 | import javafx.animation.Interpolator;
 5 | import javafx.animation.KeyFrame;
 6 | import javafx.animation.KeyValue;
 7 | import javafx.animation.Timeline;
 8 | import javafx.scene.Node;
 9 | import javafx.util.Duration;
10 | 
11 | /**
12 |  * 中间向两边变大
13 |  *
14 |  * @author blog.unclezs.com
15 |  * @since 2020.04.25 11:29
16 |  */
17 | public class CenterScaleTransition extends CachedTransition {
18 | 
19 |   public CenterScaleTransition(Node contentHolder) {
20 |     super(contentHolder, new Timeline(
21 |         new KeyFrame(Duration.ZERO,
22 |             new KeyValue(contentHolder.scaleXProperty(), 0.5, Interpolator.EASE_BOTH),
23 |             new KeyValue(contentHolder.visibleProperty(), false, Interpolator.EASE_BOTH)
24 |         ),
25 |         new KeyFrame(Duration.millis(100),
26 |             new KeyValue(contentHolder.visibleProperty(), true, Interpolator.EASE_BOTH),
27 |             new KeyValue(contentHolder.opacityProperty(), 0, Interpolator.EASE_BOTH)
28 |         ),
29 |         new KeyFrame(Duration.millis(500),
30 |             new KeyValue(contentHolder.scaleXProperty(), 1, Interpolator.EASE_BOTH),
31 |             new KeyValue(contentHolder.opacityProperty(), 1, Interpolator.EASE_BOTH)))
32 |     );
33 |     setCycleDuration(Duration.seconds(0.4));
34 |     setDelay(Duration.seconds(0));
35 |   }
36 | }
37 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/animation/RightTransition.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.animation;
 2 | 
 3 | import com.jfoenix.transitions.CachedTransition;
 4 | import javafx.animation.Interpolator;
 5 | import javafx.animation.KeyFrame;
 6 | import javafx.animation.KeyValue;
 7 | import javafx.animation.Timeline;
 8 | import javafx.scene.Node;
 9 | import javafx.util.Duration;
10 | 
11 | /**
12 |  * 左向又渐入动画
13 |  *
14 |  * @author blog.unclezs.com
15 |  * @since 2020.04.25 11:29
16 |  */
17 | public class RightTransition extends CachedTransition {
18 | 
19 |   public RightTransition(Node contentHolder) {
20 |     super(contentHolder, new Timeline(
21 |         new KeyFrame(Duration.ZERO,
22 |             new KeyValue(contentHolder.translateXProperty(), 200, Interpolator.EASE_BOTH),
23 |             new KeyValue(contentHolder.visibleProperty(), false, Interpolator.EASE_BOTH)
24 |         ),
25 |         new KeyFrame(Duration.millis(10),
26 |             new KeyValue(contentHolder.visibleProperty(), true, Interpolator.EASE_BOTH),
27 |             new KeyValue(contentHolder.opacityProperty(), 0, Interpolator.EASE_BOTH)
28 |         ),
29 |         new KeyFrame(Duration.millis(600),
30 |             new KeyValue(contentHolder.translateXProperty(), 0, Interpolator.EASE_BOTH),
31 |             new KeyValue(contentHolder.opacityProperty(), 1, Interpolator.EASE_BOTH)))
32 |     );
33 |     setCycleDuration(Duration.seconds(0.5));
34 |     setDelay(Duration.seconds(0));
35 |   }
36 | }
37 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/animation/ScaleLargeTransition.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.animation;
 2 | 
 3 | import com.jfoenix.transitions.CachedTransition;
 4 | import javafx.animation.Interpolator;
 5 | import javafx.animation.KeyFrame;
 6 | import javafx.animation.KeyValue;
 7 | import javafx.animation.Timeline;
 8 | import javafx.scene.Node;
 9 | import javafx.util.Duration;
10 | 
11 | /**
12 |  * 中心变大
13 |  *
14 |  * @author blog.unclezs.com
15 |  * @since 2020.04.25 11:29
16 |  */
17 | public class ScaleLargeTransition extends CachedTransition {
18 | 
19 |   public ScaleLargeTransition(Node contentHolder) {
20 |     super(contentHolder, new Timeline(
21 |         new KeyFrame(Duration.ZERO,
22 |             new KeyValue(contentHolder.scaleXProperty(), 0, Interpolator.EASE_BOTH),
23 |             new KeyValue(contentHolder.scaleYProperty(), 0, Interpolator.EASE_BOTH),
24 |             new KeyValue(contentHolder.visibleProperty(), false, Interpolator.EASE_BOTH)
25 |         ),
26 |         new KeyFrame(Duration.millis(100),
27 |             new KeyValue(contentHolder.visibleProperty(), true, Interpolator.EASE_BOTH),
28 |             new KeyValue(contentHolder.opacityProperty(), 0, Interpolator.EASE_BOTH)
29 |         ),
30 |         new KeyFrame(Duration.millis(666),
31 |             new KeyValue(contentHolder.scaleXProperty(), 1, Interpolator.EASE_BOTH),
32 |             new KeyValue(contentHolder.scaleYProperty(), 1, Interpolator.EASE_BOTH),
33 |             new KeyValue(contentHolder.opacityProperty(), 1, Interpolator.EASE_BOTH)))
34 |     );
35 |     setCycleDuration(Duration.seconds(0.4));
36 |     setDelay(Duration.seconds(0));
37 |   }
38 | }
39 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/animation/SmoothScrollingTransition.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.animation;
 2 | 
 3 | import com.jfoenix.transitions.CachedTransition;
 4 | import javafx.animation.Interpolator;
 5 | import javafx.animation.KeyFrame;
 6 | import javafx.animation.KeyValue;
 7 | import javafx.animation.Timeline;
 8 | import javafx.scene.control.ScrollPane;
 9 | import javafx.util.Duration;
10 | 
11 | 
12 | /**
13 |  * @author blog.unclezs.com
14 |  * @since 2020/6/19 15:18
15 |  */
16 | public class SmoothScrollingTransition extends CachedTransition {
17 | 
18 |   public SmoothScrollingTransition(ScrollPane contentHolder, double to) {
19 |     super(contentHolder, new Timeline(
20 |         new KeyFrame(Duration.millis(2000),
21 |             new KeyValue(contentHolder.vvalueProperty(), to, Interpolator.EASE_BOTH)))
22 |     );
23 |     setCycleDuration(Duration.seconds(1));
24 |     setDelay(Duration.seconds(0));
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/annotation/FxView.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.annotation;
 2 | 
 3 | import java.lang.annotation.Documented;
 4 | import java.lang.annotation.ElementType;
 5 | import java.lang.annotation.Retention;
 6 | import java.lang.annotation.RetentionPolicy;
 7 | import java.lang.annotation.Target;
 8 | 
 9 | /**
10 |  * Fxml控制器类
11 |  *
12 |  * @author blog.unclezs.com
13 |  * @since 2021/02/26 14:42
14 |  */
15 | @Documented
16 | @Target({ElementType.TYPE})
17 | @Retention(RetentionPolicy.RUNTIME)
18 | public @interface FxView {
19 | 
20 |   /**
21 |    * FXML路径
22 |    *
23 |    * @return fxml路径
24 |    */
25 |   String fxml() default "";
26 | 
27 |   /**
28 |    * 国际化资源文件
29 |    *
30 |    * @return 路径
31 |    */
32 |   String bundle() default "app";
33 | 
34 |   /**
35 |    * 样式表
36 |    *
37 |    * @return 样式表
38 |    */
39 |   String[] css() default {};
40 | 
41 |   /**
42 |    * 单例
43 |    *
44 |    * @return true 单例创建
45 |    */
46 |   boolean singleton() default true;
47 | }
48 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/appication/SceneNavigateBundle.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.appication;
 2 | 
 3 | import com.unclezs.novel.app.framework.bundle.BaseBundle;
 4 | import lombok.AccessLevel;
 5 | import lombok.Getter;
 6 | import lombok.Setter;
 7 | 
 8 | /**
 9 |  * 场景view切换 数据包
10 |  *
11 |  * @author blog.unclezs.com
12 |  * @since 2021/03/06 17:03
13 |  */
14 | public class SceneNavigateBundle extends BaseBundle {
15 | 
16 |   /**
17 |    * 来自哪个view 全限定类名
18 |    */
19 |   @Setter(AccessLevel.PACKAGE)
20 |   @Getter
21 |   private String from;
22 | }
23 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/appication/SceneView.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.appication;
 2 | 
 3 | import com.unclezs.novel.app.framework.components.StageDecorator;
 4 | import com.unclezs.novel.app.framework.core.View;
 5 | import javafx.scene.layout.Region;
 6 | import lombok.Getter;
 7 | import lombok.Setter;
 8 | 
 9 | /**
10 |  * 场景View个标记 {@link BaseApplication}
11 |  *
12 |  * @author blog.unclezs.com
13 |  * @since 2021/03/04 12:03
14 |  */
15 | public abstract class SceneView<V extends Region> extends View<V> implements StageDecorator.ActionHandler {
16 | 
17 |   @Setter
18 |   @Getter
19 |   protected BaseApplication app;
20 | 
21 |   /**
22 |    * 场景显示时候触发(场景view切换) 窗口隐藏不会被调用
23 |    *
24 |    * @param bundle 携带的数据
25 |    */
26 |   public void onShow(SceneNavigateBundle bundle) {
27 |     // do something
28 |   }
29 | 
30 |   /**
31 |    * 当场景创建完毕
32 |    */
33 |   public void onCreated() {
34 |     // do something
35 |   }
36 | }
37 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/bundle/BaseBundle.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.bundle;
 2 | 
 3 | import java.util.HashMap;
 4 | import java.util.Map;
 5 | import lombok.Getter;
 6 | 
 7 | /**
 8 |  * 数据包
 9 |  *
10 |  * @author blog.unclezs.com
11 |  * @since 2021/03/06 17:06
12 |  */
13 | @Getter
14 | @SuppressWarnings("unchecked")
15 | public class BaseBundle {
16 | 
17 |   /**
18 |    * 数据
19 |    */
20 |   protected Map<String, Object> data;
21 | 
22 | 
23 |   /**
24 |    * 添加数据
25 |    *
26 |    * @param key   键
27 |    * @param value 值
28 |    * @param <T>   类型
29 |    * @return 值
30 |    */
31 |   public <T extends BaseBundle> T put(String key, Object value) {
32 |     if (data == null) {
33 |       data = new HashMap<>(16);
34 |     }
35 |     data.put(key, value);
36 |     return (T) this;
37 |   }
38 | 
39 |   /**
40 |    * 获取数据
41 |    *
42 |    * @param key 数据key
43 |    * @param <T> 类型
44 |    * @return 数据
45 |    */
46 |   public <T> T get(String key) {
47 |     if (data == null) {
48 |       return null;
49 |     }
50 |     return (T) data.get(key);
51 |   }
52 | }
53 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/collection/SimpleObservableList.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.collection;
 2 | 
 3 | import java.util.ArrayList;
 4 | import java.util.List;
 5 | import javafx.collections.ObservableListBase;
 6 | import lombok.EqualsAndHashCode;
 7 | 
 8 | /**
 9 |  * @author blog.unclezs.com
10 |  * @since 2021/4/8 16:40
11 |  */
12 | @EqualsAndHashCode(callSuper = true)
13 | public class SimpleObservableList<E> extends ObservableListBase<E> {
14 | 
15 |   private final List<E> list;
16 | 
17 |   public SimpleObservableList(List<E> backingList) {
18 |     this.list = backingList;
19 |   }
20 | 
21 |   public SimpleObservableList() {
22 |     this.list = new ArrayList<>();
23 |   }
24 | 
25 |   @Override
26 |   public E get(int index) {
27 |     return list.get(index);
28 |   }
29 | 
30 |   @Override
31 |   public int size() {
32 |     return list.size();
33 |   }
34 | 
35 |   @Override
36 |   public E remove(int index) {
37 |     onRemove(list.get(index));
38 |     return list.remove(index);
39 |   }
40 | 
41 |   @Override
42 |   public void add(int index, E element) {
43 |     onAdd(element);
44 |     list.add(index, element);
45 |   }
46 | 
47 |   public void onAdd(E element) {
48 |     // do it
49 |   }
50 | 
51 |   public void onRemove(E element) {
52 |     // do it
53 |   }
54 | }
55 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/Loading.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components;
 2 | 
 3 | import com.jfoenix.controls.JFXAlert;
 4 | import com.jfoenix.controls.JFXButton;
 5 | import com.unclezs.novel.app.framework.core.AppContext;
 6 | import com.unclezs.novel.app.framework.util.NodeHelper;
 7 | import com.unclezs.novel.app.framework.util.ResourceUtils;
 8 | import javafx.scene.image.Image;
 9 | import javafx.scene.image.ImageView;
10 | import javafx.scene.layout.VBox;
11 | import javafx.stage.Window;
12 | import lombok.Setter;
13 | 
14 | /**
15 |  * loading组件
16 |  *
17 |  * @author blog.unclezs.com
18 |  * @since 2021/4/15 21:31
19 |  */
20 | public class Loading extends JFXAlert<Object> {
21 | 
22 |   private static final Image LOADING_IMAGE = new Image(ResourceUtils.externalForm("images/loading.gif"));
23 |   @Setter
24 |   private Runnable onCancel;
25 | 
26 |   public Loading(Window window) {
27 |     super(window);
28 |   }
29 | 
30 |   public Loading() {
31 |     super(AppContext.getInstance().getPrimaryStage());
32 |     setOverlayClose(false);
33 |     JFXButton cancel = new JFXButton("取消");
34 |     cancel.setOnAction(e -> {
35 |       if (onCancel != null) {
36 |         onCancel.run();
37 |       }
38 |       hideWithAnimation();
39 |     });
40 |     setContent(NodeHelper.addClass(new VBox(new ImageView(LOADING_IMAGE), cancel), "loading"));
41 |   }
42 | }
43 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/NumberTextField.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components;
 2 | 
 3 | import cn.hutool.core.util.NumberUtil;
 4 | import java.util.function.UnaryOperator;
 5 | import javafx.scene.control.TextField;
 6 | import javafx.scene.control.TextFormatter;
 7 | import javafx.scene.control.TextFormatter.Change;
 8 | 
 9 | /**
10 |  * 限制输入数字的输入框
11 |  *
12 |  * @author blog.unclezs.com
13 |  * @since 2021/4/21 23:43
14 |  */
15 | public class NumberTextField extends TextField {
16 | 
17 |   /**
18 |    * 限制输入输入的formatter
19 |    */
20 |   public static final TextFormatter<Change> NUMBER = new TextFormatter<>(new NumberUnaryOperator());
21 | 
22 |   public NumberTextField() {
23 |     this(null);
24 |   }
25 | 
26 |   public NumberTextField(String text) {
27 |     super(text);
28 |     setTextFormatter(NUMBER);
29 |   }
30 | 
31 |   /**
32 |    * 限制输入数字
33 |    */
34 |   static class NumberUnaryOperator implements UnaryOperator<Change> {
35 | 
36 | 
37 |     @Override
38 |     public Change apply(Change change) {
39 |       if (change.isDeleted()) {
40 |         return change;
41 |       }
42 |       if (NumberUtil.isNumber(change.getText())) {
43 |         return change;
44 |       }
45 |       return null;
46 |     }
47 |   }
48 | }
49 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/PlaceHolder.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components;
 2 | 
 3 | import com.unclezs.novel.app.framework.components.icon.Icon;
 4 | import com.unclezs.novel.app.framework.components.icon.IconFont;
 5 | import com.unclezs.novel.app.framework.util.NodeHelper;
 6 | import javafx.beans.NamedArg;
 7 | import javafx.geometry.Pos;
 8 | import javafx.scene.control.Label;
 9 | import javafx.scene.layout.VBox;
10 | import lombok.Getter;
11 | 
12 | /**
13 |  * @author blog.unclezs.com
14 |  * @since 2021/5/5 13:57
15 |  */
16 | public class PlaceHolder extends VBox {
17 | 
18 |   private final Label label;
19 |   @Getter
20 |   private String tip;
21 | 
22 |   public PlaceHolder(@NamedArg("tip") String tip) {
23 |     NodeHelper.addClass(this, "placeholder");
24 |     label = NodeHelper.addClass(new Label(tip), "tip");
25 |     this.getChildren().addAll(new Icon(IconFont.EMPTY), label);
26 |     this.setAlignment(Pos.CENTER);
27 |   }
28 | 
29 |   public void setTip(String tip) {
30 |     this.tip = tip;
31 |     this.label.setText(tip);
32 |   }
33 | }
34 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/SelectableButton.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components;
 2 | 
 3 | import com.unclezs.novel.app.framework.components.icon.IconButton;
 4 | import javafx.beans.property.ReadOnlyBooleanProperty;
 5 | import javafx.beans.property.ReadOnlyBooleanWrapper;
 6 | import javafx.css.PseudoClass;
 7 | 
 8 | /**
 9 |  * 可以选择的按钮
10 |  *
11 |  * @author blog.unclezs.com
12 |  * @since 2021/03/02 18:03
13 |  */
14 | public class SelectableButton extends IconButton {
15 | 
16 |   /**
17 |    * 设置选中伪类
18 |    */
19 |   private static final PseudoClass SELECTED_PSEUDO_CLASS_STATE = PseudoClass.getPseudoClass("selected");
20 |   private ReadOnlyBooleanWrapper selected;
21 | 
22 |   public boolean isSelected() {
23 |     return selectedProperty().get();
24 |   }
25 | 
26 |   public void setSelected(boolean selected) {
27 |     selectedPropertyImpl().set(selected);
28 |   }
29 | 
30 |   public ReadOnlyBooleanProperty selectedProperty() {
31 |     return selectedPropertyImpl().getReadOnlyProperty();
32 |   }
33 | 
34 |   public ReadOnlyBooleanWrapper selectedPropertyImpl() {
35 |     if (selected == null) {
36 |       selected = new ReadOnlyBooleanWrapper(this, "selected", false) {
37 |         @Override
38 |         protected void invalidated() {
39 |           pseudoClassStateChanged(SELECTED_PSEUDO_CLASS_STATE, get());
40 |         }
41 |       };
42 |     }
43 |     return selected;
44 |   }
45 | }
46 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/TabButton.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components;
 2 | 
 3 | import javafx.event.ActionEvent;
 4 | import lombok.Getter;
 5 | 
 6 | /**
 7 |  * @author blog.unclezs.com
 8 |  * @since 2021/4/30 9:26
 9 |  */
10 | public class TabButton extends SelectableButton {
11 | 
12 |   public static final String DEFAULT_STYLE_CLASS = "tab-button";
13 |   @Getter
14 |   private TabGroup tabGroup;
15 | 
16 |   public TabButton() {
17 |     getStyleClass().add(DEFAULT_STYLE_CLASS);
18 |     this.addEventFilter(ActionEvent.ACTION, event -> {
19 |       if (!isSelected()) {
20 |         setSelected(true);
21 |       }
22 |     });
23 |   }
24 | 
25 |   /**
26 |    * 设置tab组
27 |    *
28 |    * @param tabGroup 组
29 |    */
30 |   public void setTabGroup(TabGroup tabGroup) {
31 |     this.tabGroup = tabGroup;
32 |     this.tabGroup.getTabs().add(this);
33 |   }
34 | 
35 |   @Override
36 |   public void setSelected(boolean selected) {
37 |     super.setSelected(selected);
38 |     if (selected && tabGroup != null) {
39 |       tabGroup.selectTab(this);
40 |     }
41 |   }
42 | }
43 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/TabGroup.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components;
 2 | 
 3 | import java.util.ArrayList;
 4 | import java.util.List;
 5 | import java.util.Objects;
 6 | import java.util.function.Consumer;
 7 | import lombok.Getter;
 8 | import lombok.Setter;
 9 | 
10 | /**
11 |  * @author blog.unclezs.com
12 |  * @since 2021/4/30 9:38
13 |  */
14 | public class TabGroup {
15 | 
16 |   @Getter
17 |   private final List<TabButton> tabs = new ArrayList<>();
18 |   @Setter
19 |   private Consumer<TabButton> onSelected;
20 | 
21 |   /**
22 |    * 设置选中tab
23 |    *
24 |    * @param tabButton tab
25 |    */
26 |   public void selectTab(TabButton tabButton) {
27 |     for (TabButton tab : tabs) {
28 |       if (tab != tabButton) {
29 |         tab.setSelected(false);
30 |       } else if (onSelected != null) {
31 |         onSelected.accept(tab);
32 |       }
33 |     }
34 |   }
35 | 
36 |   public TabButton findTab(Object userData) {
37 |     for (TabButton tab : tabs) {
38 |       if (Objects.equals(userData, tab.getUserData())) {
39 |         return tab;
40 |       }
41 |     }
42 |     return null;
43 |   }
44 | }
45 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/Tag.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components;
 2 | 
 3 | import com.unclezs.novel.app.framework.util.NodeHelper;
 4 | import javafx.scene.control.Label;
 5 | 
 6 | /**
 7 |  * @author blog.unclezs.com
 8 |  * @since 2021/4/17 2:26
 9 |  */
10 | public class Tag extends Label {
11 | 
12 |   public Tag() {
13 |     this(null);
14 |   }
15 | 
16 |   public Tag(String text) {
17 |     super(text);
18 |     NodeHelper.addClass(this, "tag");
19 |   }
20 | }
21 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/TitleBar.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components;
 2 | 
 3 | import com.unclezs.novel.app.framework.util.NodeHelper;
 4 | import javafx.beans.NamedArg;
 5 | import javafx.scene.Node;
 6 | import javafx.scene.control.Label;
 7 | import javafx.scene.layout.HBox;
 8 | import javafx.scene.layout.Priority;
 9 | import lombok.Getter;
10 | import lombok.Setter;
11 | 
12 | /**
13 |  * 标题栏
14 |  *
15 |  * @author blog.unclezs.com
16 |  * @since 2021/4/25 12:51
17 |  */
18 | @Getter
19 | @Setter
20 | public class TitleBar extends HBox {
21 | 
22 |   public static final String DEFAULT_STYLE_CLASS = "title-bar";
23 | 
24 |   private String title;
25 |   private Node content;
26 | 
27 |   public TitleBar(@NamedArg("title") String title) {
28 |     this(title, null);
29 |   }
30 | 
31 |   public TitleBar(@NamedArg("title") String title, @NamedArg("content") Node content) {
32 |     NodeHelper.addClass(this, DEFAULT_STYLE_CLASS);
33 | 
34 |     Label titleLabel = NodeHelper.addClass(new Label(title), "title");
35 |     titleLabel.setMaxWidth(Double.MAX_VALUE);
36 |     HBox.setHgrow(titleLabel, Priority.ALWAYS);
37 | 
38 |     if (content != null) {
39 |       NodeHelper.addClass(content, "content");
40 |       getChildren().addAll(titleLabel, content);
41 |     } else {
42 |       getChildren().add(titleLabel);
43 |     }
44 |   }
45 | }
46 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/cell/BaseListCell.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components.cell;
 2 | 
 3 | import javafx.scene.control.ListCell;
 4 | 
 5 | /**
 6 |  * listCell
 7 |  *
 8 |  * @author blog.unclezs.com
 9 |  * @since 2021/5/5 19:52
10 |  */
11 | public abstract class BaseListCell<T> extends ListCell<T> {
12 | 
13 |   @Override
14 |   protected final void updateItem(T item, boolean empty) {
15 |     super.updateItem(item, empty);
16 |     if (item == null || empty) {
17 |       setGraphic(null);
18 |       setText(null);
19 |     } else {
20 |       updateItem(item);
21 |     }
22 |   }
23 | 
24 |   /**
25 |    * 更新item
26 |    *
27 |    * @param item T
28 |    */
29 |   protected abstract void updateItem(T item);
30 | }
31 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/properties/Str.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components.properties;
 2 | 
 3 | import javafx.beans.DefaultProperty;
 4 | import lombok.Data;
 5 | import lombok.NoArgsConstructor;
 6 | 
 7 | /**
 8 |  * 用于fxml的字符串
 9 |  *
10 |  * @author blog.unclezs.com
11 |  * @since 2021/4/16 16:04
12 |  */
13 | @Data
14 | @NoArgsConstructor
15 | @DefaultProperty("value")
16 | public class Str {
17 | 
18 |   /**
19 |    * 字符串值
20 |    */
21 |   private String value;
22 | 
23 | }
24 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/sidebar/SidebarMenu.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components.sidebar;
 2 | 
 3 | import javafx.beans.DefaultProperty;
 4 | import javafx.collections.FXCollections;
 5 | import javafx.collections.ObservableList;
 6 | import javafx.scene.control.Label;
 7 | import lombok.Getter;
 8 | import lombok.Setter;
 9 | 
10 | /**
11 |  * 侧边导航栏菜单
12 |  *
13 |  * @author blog.unclezs.com
14 |  * @since 2021/02/27 11:25
15 |  */
16 | @Getter
17 | @Setter
18 | @DefaultProperty("menuItems")
19 | public class SidebarMenu extends Label {
20 | 
21 |   public static final String GROUP_LABEL_CLASS = "sidebar-menu";
22 |   private ObservableList<SidebarMenuItem> menuItems = FXCollections.observableArrayList();
23 |   private String name;
24 | 
25 |   public SidebarMenu() {
26 |     this.getStyleClass().add(GROUP_LABEL_CLASS);
27 |   }
28 | 
29 |   /**
30 |    * 设置分组名称
31 |    *
32 |    * @param name 名称
33 |    */
34 |   public void setName(String name) {
35 |     this.name = name;
36 |     this.setText(name);
37 |   }
38 | }
39 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/sidebar/SidebarMenuItem.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components.sidebar;
 2 | 
 3 | import com.unclezs.novel.app.framework.components.SelectableButton;
 4 | import com.unclezs.novel.app.framework.core.AppContext;
 5 | import com.unclezs.novel.app.framework.util.ReflectUtils;
 6 | import javafx.scene.Parent;
 7 | import lombok.Getter;
 8 | import lombok.Setter;
 9 | 
10 | /**
11 |  * menu button
12 |  *
13 |  * @author blog.unclezs.com
14 |  * @since 2021/02/26 18:33
15 |  */
16 | @Getter
17 | @Setter
18 | public class SidebarMenuItem extends SelectableButton {
19 | 
20 |   private static final String DEFAULT_STYLE_CLASS = "sidebar-menu-item";
21 |   /**
22 |    * 视图的全限定类名
23 |    */
24 |   private String view;
25 |   /**
26 |    * 跳转的视图
27 |    */
28 |   private SidebarView<? extends Parent> sidebarView;
29 |   private SidebarNavigation navigation;
30 | 
31 |   public SidebarMenuItem() {
32 |     getStyleClass().add(DEFAULT_STYLE_CLASS);
33 |     // 点击触发菜单切换
34 |     setOnAction(e -> {
35 |       SidebarNavigateBundle bundle = new SidebarNavigateBundle();
36 |       bundle.setMenuTrigger(true);
37 |       // 未加载则加载
38 |       if (sidebarView == null) {
39 |         this.sidebarView = AppContext.getView(ReflectUtils.forName(view));
40 |         this.sidebarView.setNavigation(navigation);
41 |       }
42 |       navigation.navigate(sidebarView, bundle);
43 |     });
44 |   }
45 | 
46 |   /**
47 |    * 设置按钮单击跳转到的页面
48 |    *
49 |    * @param view view controller
50 |    */
51 |   public void setView(String view) {
52 |     this.view = view;
53 |     // 注释掉此行则进行导航栏页面懒加载
54 |     this.sidebarView = AppContext.getView(ReflectUtils.forName(view));
55 |   }
56 | }
57 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/sidebar/SidebarNavigateBundle.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components.sidebar;
 2 | 
 3 | import com.unclezs.novel.app.framework.bundle.BaseBundle;
 4 | import java.util.Objects;
 5 | import lombok.AccessLevel;
 6 | import lombok.Data;
 7 | import lombok.EqualsAndHashCode;
 8 | import lombok.NoArgsConstructor;
 9 | import lombok.Setter;
10 | 
11 | /**
12 |  * 侧边导航 跳转数据
13 |  *
14 |  * @author blog.unclezs.com
15 |  * @since 2021/03/05 14:41
16 |  */
17 | @Data
18 | @EqualsAndHashCode(callSuper = true)
19 | @NoArgsConstructor
20 | public class SidebarNavigateBundle extends BaseBundle {
21 | 
22 |   /**
23 |    * 来自哪个view 全限定类名
24 |    */
25 |   @Setter(AccessLevel.PACKAGE)
26 |   private String from;
27 |   /**
28 |    * 是否由菜单触发
29 |    */
30 |   private boolean menuTrigger;
31 |   /**
32 |    * 跳转的标记
33 |    */
34 |   private int flag;
35 | 
36 |   /**
37 |    * 是否来自某个页面
38 |    *
39 |    * @param clazz 页面
40 |    * @return true 是
41 |    */
42 |   public boolean isFrom(Class<? extends SidebarView<?>> clazz) {
43 |     return Objects.equals(clazz.getName(), from);
44 |   }
45 | }
46 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/components/sidebar/SidebarView.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.components.sidebar;
 2 | 
 3 | 
 4 | import com.unclezs.novel.app.framework.core.View;
 5 | import javafx.scene.Parent;
 6 | import lombok.EqualsAndHashCode;
 7 | import lombok.Getter;
 8 | import lombok.Setter;
 9 | 
10 | /**
11 |  * 侧边菜单视图控制器基类
12 |  *
13 |  * @author blog.unclezs.com
14 |  * @since 2021/03/05 14:12
15 |  */
16 | @Getter
17 | @Setter
18 | @EqualsAndHashCode(callSuper = false)
19 | public class SidebarView<V extends Parent> extends View<V> {
20 | 
21 |   protected SidebarNavigation navigation;
22 | 
23 |   /**
24 |    * 侧边栏被创建完成时调用(navigation已装配好)
25 |    */
26 |   public void onCreated() {
27 |     // do something
28 |   }
29 | 
30 |   /**
31 |    * 页面显示之前 处理数据
32 |    *
33 |    * @param bundle 页面跳转数据
34 |    */
35 |   public void onShow(SidebarNavigateBundle bundle) {
36 |     // do something
37 |   }
38 | 
39 |   /**
40 |    * 页面显示之后 处理数据
41 |    *
42 |    * @param bundle 页面跳转数据
43 |    */
44 |   public void onShown(SidebarNavigateBundle bundle) {
45 |     // do something
46 |   }
47 | 
48 |   /**
49 |    * 注入导航,并触发onCreated
50 |    *
51 |    * @param navigation 导航
52 |    */
53 |   public void setNavigation(SidebarNavigation navigation) {
54 |     this.navigation = navigation;
55 |     this.onCreated();
56 |   }
57 | }
58 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/core/ContextDestroyedListener.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.core;
 2 | 
 3 | /**
 4 |  * @author blog.unclezs.com
 5 |  * @since 2021/4/25 11:22
 6 |  */
 7 | public interface ContextDestroyedListener {
 8 | 
 9 |   void contextDestroyed(AppContext context);
10 | }
11 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/core/View.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.core;
 2 | 
 3 | import com.unclezs.novel.app.framework.support.LocalizedSupport;
 4 | import java.net.URL;
 5 | import java.util.ResourceBundle;
 6 | import javafx.fxml.Initializable;
 7 | import lombok.Getter;
 8 | import lombok.Setter;
 9 | 
10 | /**
11 |  * @param <V> 视图类型
12 |  * @author blog.unclezs.com
13 |  * @since 2021/4/12 9:52
14 |  */
15 | @Getter
16 | @Setter
17 | public class View<V> implements Initializable, LocalizedSupport {
18 | 
19 |   /**
20 |    * 国际化资源包
21 |    */
22 |   protected ResourceBundle bundle;
23 |   /**
24 |    * 控制器对应的视图
25 |    */
26 |   protected V root;
27 | 
28 |   /**
29 |    * view创建之后,只做了fxml的属性注入,还没有做应用属性注入(如sidebar的navigation)
30 |    */
31 |   public void onCreate() {
32 |     // Bean创建完成
33 |   }
34 | 
35 |   /**
36 |    * 被隐藏(场景view切换) 窗口隐藏不会被调用
37 |    */
38 |   public void onHidden() {
39 |     // do something
40 |   }
41 | 
42 |   /**
43 |    * view被销毁时调用
44 |    */
45 |   public void onDestroy() {
46 |     onHidden();
47 |     // 一般为程序退出保持数据
48 |   }
49 | 
50 |   @Override
51 |   public final void initialize(URL location, ResourceBundle resources) {
52 |     if (resources != null) {
53 |       this.bundle = resources;
54 |     }
55 |     onCreate();
56 |   }
57 | }
58 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/exception/BaseRuntimeException.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.exception;
 2 | 
 3 | import lombok.NoArgsConstructor;
 4 | import org.slf4j.helpers.MessageFormatter;
 5 | 
 6 | /**
 7 |  * 带格式化的异常
 8 |  *
 9 |  * @author blog.unclezs.com
10 |  * @since 2021/4/11 20:45
11 |  */
12 | @NoArgsConstructor
13 | public class BaseRuntimeException extends RuntimeException {
14 | 
15 |   public BaseRuntimeException(String message, Object... params) {
16 |     super(MessageFormatter.arrayFormat(message, params).getMessage(), MessageFormatter.getThrowableCandidate(params));
17 |   }
18 | 
19 |   public BaseRuntimeException(Throwable cause) {
20 |     super(cause);
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/exception/FxException.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.exception;
 2 | 
 3 | /**
 4 |  * @author blog.unclezs.com
 5 |  * @since 2021/02/26 15:15
 6 |  */
 7 | public class FxException extends BaseRuntimeException {
 8 | 
 9 |   public FxException() {
10 |   }
11 | 
12 |   public FxException(String message, Object... params) {
13 |     super(message, params);
14 |   }
15 | 
16 |   public FxException(Throwable cause) {
17 |     super(cause);
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/exception/IoRuntimeException.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.exception;
 2 | 
 3 | /**
 4 |  * @author blog.unclezs.com
 5 |  * @since 2021/4/13 0:38
 6 |  */
 7 | public class IoRuntimeException extends BaseRuntimeException {
 8 | 
 9 |   public IoRuntimeException() {
10 |   }
11 | 
12 |   public IoRuntimeException(String message, Object... params) {
13 |     super(message, params);
14 |   }
15 | 
16 |   public IoRuntimeException(Throwable cause) {
17 |     super(cause);
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/exception/ReflectionException.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.exception;
 2 | 
 3 | /**
 4 |  * @author blog.unclezs.com
 5 |  * @since 2021/03/05 15:52
 6 |  */
 7 | public class ReflectionException extends RuntimeException {
 8 | 
 9 |   public ReflectionException() {
10 |   }
11 | 
12 |   public ReflectionException(String message) {
13 |     super(message);
14 |   }
15 | 
16 |   public ReflectionException(String message, Throwable cause) {
17 |     super(message, cause);
18 |   }
19 | 
20 |   public ReflectionException(Throwable cause) {
21 |     super(cause);
22 |   }
23 | 
24 |   public ReflectionException(String message, Throwable cause, boolean enableSuppression,
25 |       boolean writableStackTrace) {
26 |     super(message, cause, enableSuppression, writableStackTrace);
27 |   }
28 | }
29 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/exception/ResourceNotFoundException.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.exception;
 2 | 
 3 | /**
 4 |  * 资源未找到
 5 |  *
 6 |  * @author blog.unclezs.com
 7 |  * @since 2021/4/11 20:43
 8 |  */
 9 | public class ResourceNotFoundException extends BaseRuntimeException {
10 | 
11 |   public ResourceNotFoundException(String message, Object... params) {
12 |     super(message, params);
13 |   }
14 | 
15 |   public ResourceNotFoundException(String resource) {
16 |     this("资源未找到:{}", resource);
17 |   }
18 | }
19 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/executor/DebounceTask.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.executor;
 2 | 
 3 | import java.util.concurrent.ScheduledFuture;
 4 | 
 5 | /**
 6 |  * 防抖任务
 7 |  *
 8 |  * @author blog.unclezs.com
 9 |  * @since 2021/6/6 20:13
10 |  */
11 | public class DebounceTask {
12 | 
13 |   private final Long delay;
14 |   private final Runnable runnable;
15 |   private final boolean fx;
16 |   private ScheduledFuture<?> scheduledFuture;
17 | 
18 |   public DebounceTask(Runnable runnable, Long delay) {
19 |     this(runnable, delay, true);
20 |   }
21 | 
22 |   public DebounceTask(Runnable runnable, Long delay, boolean fx) {
23 |     this.runnable = runnable;
24 |     this.delay = delay;
25 |     this.fx = fx;
26 |   }
27 | 
28 |   public static DebounceTask build(Runnable runnable, Long delay) {
29 |     return new DebounceTask(runnable, delay);
30 |   }
31 | 
32 |   public static DebounceTask build(Runnable runnable, Long delay, boolean fx) {
33 |     return new DebounceTask(runnable, delay, fx);
34 |   }
35 | 
36 | 
37 |   public void run() {
38 |     if (scheduledFuture != null) {
39 |       scheduledFuture.cancel(true);
40 |     }
41 |     scheduledFuture = Executor.run(() -> {
42 |       scheduledFuture = null;
43 |       if (fx) {
44 |         Executor.runFxAndWait(runnable);
45 |       } else {
46 |         Executor.run(runnable);
47 |       }
48 |     }, delay);
49 |   }
50 | }
51 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/executor/TaskFactory.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.executor;
 2 | 
 3 | import java.util.concurrent.Callable;
 4 | import lombok.experimental.UtilityClass;
 5 | 
 6 | /**
 7 |  * @author blog.unclezs.com
 8 |  * @since 2021/4/15 20:48
 9 |  */
10 | @UtilityClass
11 | public class TaskFactory {
12 | 
13 |   /**
14 |    * 创建loading任务
15 |    *
16 |    * @param task 任务内容
17 |    * @param <R>  返回值类型
18 |    * @return 任务
19 |    */
20 |   public static <R> FluentTask<R> create(Callable<R> task) {
21 |     return create(true, task);
22 |   }
23 | 
24 |   /**
25 |    * 创建loading任务
26 |    *
27 |    * @param task    任务内容
28 |    * @param <R>     返回值类型
29 |    * @param loading 显示loading
30 |    * @return 任务
31 |    */
32 |   public static <R> FluentTask<R> create(boolean loading, Callable<R> task) {
33 |     return new FluentTask<>(loading) {
34 |       @Override
35 |       protected R call() throws Exception {
36 |         return task.call();
37 |       }
38 |     };
39 |   }
40 | }
41 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/serialize/ListPropertyTypeAdapter.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.serialize;
 2 | 
 3 | import com.google.gson.JsonArray;
 4 | import com.google.gson.JsonDeserializationContext;
 5 | import com.google.gson.JsonDeserializer;
 6 | import com.google.gson.JsonElement;
 7 | import com.google.gson.JsonParseException;
 8 | import com.google.gson.JsonSerializationContext;
 9 | import com.google.gson.JsonSerializer;
10 | import java.lang.reflect.ParameterizedType;
11 | import java.lang.reflect.Type;
12 | import javafx.collections.FXCollections;
13 | import javafx.collections.ObservableList;
14 | 
15 | /**
16 |  * 序列化List
17 |  *
18 |  * @author blog.unclezs.com
19 |  * @since 2021/4/28 21:13
20 |  */
21 | public class ListPropertyTypeAdapter implements JsonSerializer<ObservableList<Object>>, JsonDeserializer<ObservableList<Object>> {
22 | 
23 |   @Override
24 |   public JsonElement serialize(ObservableList<Object> list, Type type, JsonSerializationContext jsonSerializationContext) {
25 |     JsonArray array = new JsonArray();
26 |     for (Object o : list) {
27 |       array.add(jsonSerializationContext.serialize(o));
28 |     }
29 |     return array;
30 |   }
31 | 
32 |   @Override
33 |   public ObservableList<Object> deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
34 |     Type actualType = ((ParameterizedType) type).getActualTypeArguments()[0];
35 |     JsonArray array = jsonElement.getAsJsonArray();
36 |     ObservableList<Object> list = FXCollections.observableArrayList();
37 |     for (JsonElement element : array) {
38 |       list.add(jsonDeserializationContext.deserialize(element, actualType));
39 |     }
40 |     return list;
41 |   }
42 | }
43 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/support/LocalizedSupport.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.support;
 2 | 
 3 | import java.util.ResourceBundle;
 4 | 
 5 | /**
 6 |  * 国际化支持
 7 |  *
 8 |  * @author blog.unclezs.com
 9 |  * @since 2021/02/26 12:05
10 |  */
11 | public interface LocalizedSupport {
12 | 
13 |   String BASE_NAME = "com.unclezs.novel.app.localized.";
14 |   String BASE_BUNDLE_NAME = "app";
15 |   String COMMON_BUNDLE_NAME = "widgets.common";
16 | 
17 |   /**
18 |    * 获取国际化资源文件
19 |    *
20 |    * @param bundleName 资源包名称
21 |    * @return ResourceBundle
22 |    */
23 |   static ResourceBundle getBundle(String bundleName) {
24 |     return ResourceBundle.getBundle(BASE_NAME.concat(bundleName));
25 |   }
26 | 
27 |   /**
28 |    * 从app.properties中读取
29 |    *
30 |    * @param key key
31 |    * @return 国际化后的
32 |    */
33 |   static String app(String key) {
34 |     return LocalizedSupport.getBundle(LocalizedSupport.BASE_BUNDLE_NAME).getString(key);
35 |   }
36 | 
37 |   /**
38 |    * 读取国际化字符串
39 |    *
40 |    * @param key 字符串key
41 |    * @return 国际化字符串
42 |    */
43 |   default String localized(String key) {
44 |     ResourceBundle bundle = getBundle();
45 |     if (bundle != null) {
46 |       return bundle.getString(key);
47 |     }
48 |     if (getBundleName() == null) {
49 |       throw new UnsupportedOperationException("未配置国际化资源包");
50 |     }
51 |     return getBundle(getBundleName()).getString(key);
52 |   }
53 | 
54 |   /**
55 |    * 获取 Bundle BaseName
56 |    *
57 |    * @return bundle BaseName
58 |    */
59 |   default String getBundleName() {
60 |     return BASE_BUNDLE_NAME;
61 |   }
62 | 
63 |   /**
64 |    * 获取 Bundle
65 |    *
66 |    * @return bundle
67 |    */
68 |   default ResourceBundle getBundle() {
69 |     return null;
70 |   }
71 | }
72 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/support/fonts/FontsLoader.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.support.fonts;
 2 | 
 3 | import cn.hutool.core.io.FileUtil;
 4 | import cn.hutool.core.util.URLUtil;
 5 | import java.io.File;
 6 | import javafx.scene.text.Font;
 7 | import lombok.experimental.UtilityClass;
 8 | import lombok.extern.slf4j.Slf4j;
 9 | 
10 | /**
11 |  * @author blog.unclezs.com
12 |  * @since 2021/6/5 16:58
13 |  */
14 | @Slf4j
15 | @UtilityClass
16 | public class FontsLoader {
17 | 
18 |   /**
19 |    * 加载文件夹下的所有字体
20 |    *
21 |    * @param dir 字体文件夹
22 |    */
23 |   public void loadFonts(File dir) {
24 |     if (!dir.exists()) {
25 |       log.debug("字体文件夹不存在:{}", dir);
26 |       return;
27 |     }
28 |     String[] fonts = dir.list();
29 |     if (fonts == null) {
30 |       log.debug("未发现字体:{}", dir);
31 |       return;
32 |     }
33 |     for (String filename : fonts) {
34 |       try {
35 |         String fontUrl = URLUtil.getURL(FileUtil.file(dir, filename)).toExternalForm();
36 |         Font font = Font.loadFont(fontUrl, Font.getDefault().getSize());
37 |         if (font != null) {
38 |           log.debug("加载{}字体成功", font.getFamily());
39 |         } else {
40 |           log.error("加载{}字体失败", filename);
41 |         }
42 |       } catch (Exception e) {
43 |         log.error("加载{}字体失败", filename, e);
44 |       }
45 |     }
46 |   }
47 | }
48 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/util/BindUtils.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.util;
 2 | 
 3 | import lombok.experimental.UtilityClass;
 4 | 
 5 | /**
 6 |  * 绑定工具
 7 |  *
 8 |  * @author blog.unclezs.com
 9 |  * @since 2021/4/21 20:15
10 |  */
11 | @UtilityClass
12 | public class BindUtils {
13 | 
14 | //  public static void binds(Property<String> property, Supplier<String> getter, Consumer<String> setter) {
15 | //    SimpleStringProperty bindProperty = new SimpleStringProperty(getter.get());
16 | //    property.bindBidirectional(bindProperty);
17 | //    bindProperty.addListener(e -> setter.accept(bindProperty.get()));
18 | //  }
19 | //
20 | //  public static void bind(Property<String> property, Supplier<Integer> getter, Consumer<Integer> setter) {
21 | //    Integer initValue = getter.get();
22 | //    SimpleStringProperty bindProperty = new SimpleStringProperty(initValue == null ? null : String.valueOf(initValue));
23 | //    property.bindBidirectional(bindProperty);
24 | //    bindProperty.addListener(e -> setter.accept(Integer.parseInt(bindProperty.get())));
25 | //  }
26 | 
27 | }
28 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/util/Choosers.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.util;
 2 | 
 3 | import com.unclezs.novel.app.framework.core.AppContext;
 4 | import java.io.File;
 5 | import javafx.stage.DirectoryChooser;
 6 | import javafx.stage.FileChooser;
 7 | import javafx.stage.FileChooser.ExtensionFilter;
 8 | import lombok.experimental.UtilityClass;
 9 | 
10 | /**
11 |  * @author blog.unclezs.com
12 |  * @since 2021/5/29 1:34
13 |  */
14 | @UtilityClass
15 | public class Choosers {
16 | 
17 |   private static File dir;
18 | 
19 |   /**
20 |    * 选择图片
21 |    *
22 |    * @param tip 提示
23 |    * @return 图片
24 |    */
25 |   public static File chooseImage(String tip) {
26 |     FileChooser chooser = new FileChooser();
27 |     chooser.setInitialDirectory(dir);
28 |     chooser.getExtensionFilters().add(new ExtensionFilter(tip, "*.jpg", "*.png", "*.jpeg"));
29 |     return updateDir(chooser.showOpenDialog(AppContext.getInstance().getPrimaryStage()));
30 |   }
31 | 
32 |   /**
33 |    * 选择文件夹
34 |    *
35 |    * @return 文件夹
36 |    */
37 |   public static File chooseFolder() {
38 |     DirectoryChooser chooser = new DirectoryChooser();
39 |     chooser.setInitialDirectory(dir);
40 |     return updateDir(chooser.showDialog(AppContext.getInstance().getPrimaryStage()));
41 |   }
42 | 
43 |   /**
44 |    * 选择文件
45 |    *
46 |    * @return 文件
47 |    */
48 |   public static File chooseFile() {
49 |     FileChooser chooser = new FileChooser();
50 |     chooser.setInitialDirectory(dir);
51 |     return updateDir(chooser.showOpenDialog(AppContext.getInstance().getPrimaryStage()));
52 |   }
53 | 
54 |   private static File updateDir(File file) {
55 |     if (file != null) {
56 |       if (file.isDirectory()) {
57 |         dir = file;
58 |       } else {
59 |         dir = file.getParentFile();
60 |       }
61 |     }
62 |     return file;
63 |   }
64 | 
65 | }
66 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/util/ColorUtil.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.util;
 2 | 
 3 | import java.util.Locale;
 4 | import javafx.scene.paint.Color;
 5 | 
 6 | /**
 7 |  * @author blog.unclezs.com
 8 |  * @since 2020/5/12 19:30
 9 |  */
10 | public class ColorUtil {
11 | 
12 |   public static String colorToHex(Color c) {
13 |     if (c != null) {
14 |       return String.format((Locale) null, "#%02x%02x%02x",
15 |           Math.round(c.getRed() * 255),
16 |           Math.round(c.getGreen() * 255),
17 |           Math.round(c.getBlue() * 255)).toUpperCase();
18 |     } else {
19 |       return null;
20 |     }
21 |   }
22 | 
23 |   /**
24 |    * 字体颜色 rgb
25 |    *
26 |    * @return /
27 |    */
28 |   public static String getFontRgbColor(String color) {
29 |     return getFontRgbColor(Color.valueOf(color));
30 |   }
31 | 
32 |   /**
33 |    * 字体颜色 rgb 黑底白字
34 |    *
35 |    * @return /
36 |    */
37 |   public static String getFontRgbColor(Color color) {
38 |     return isWhiteFontColor(color) ? "rgba(255, 255, 255, 0.87)" : "rgba(0, 0, 0, 0.87)";
39 |   }
40 | 
41 |   /**
42 |    * 是否为白色字体
43 |    *
44 |    * @return /
45 |    */
46 |   public static Boolean isWhiteFontColor(Color color) {
47 |     return color.grayscale().getRed() < 0.5;
48 |   }
49 | 
50 |   /**
51 |    * 是否为白色字体
52 |    *
53 |    * @return /
54 |    */
55 |   public static Boolean isWhiteFontColor(String colorHex) {
56 |     return isWhiteFontColor(Color.valueOf(colorHex));
57 |   }
58 | }
59 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/util/DesktopUtils.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.util;
 2 | 
 3 | import cn.hutool.core.io.IORuntimeException;
 4 | import com.unclezs.novel.app.framework.components.Toast;
 5 | import java.awt.Desktop;
 6 | import java.io.File;
 7 | import java.io.IOException;
 8 | import java.net.URI;
 9 | import javafx.scene.input.Clipboard;
10 | import javafx.scene.input.ClipboardContent;
11 | import javafx.scene.input.DataFormat;
12 | import lombok.experimental.UtilityClass;
13 | import lombok.extern.slf4j.Slf4j;
14 | 
15 | /**
16 |  * 桌面工具
17 |  *
18 |  * @author blog.unclezs.com
19 |  * @since 2021/4/21 9:35
20 |  */
21 | @Slf4j
22 | @UtilityClass
23 | public class DesktopUtils {
24 | 
25 |   /**
26 |    * 浏览器打开
27 |    *
28 |    * @param url 地址
29 |    */
30 |   public static void openBrowse(String url) {
31 |     try {
32 |       Desktop.getDesktop().browse(new URI(url));
33 |     } catch (Exception e) {
34 |       log.error("链接打开失败:{}", url, e);
35 |       throw new IORuntimeException(e);
36 |     }
37 |   }
38 | 
39 |   /**
40 |    * 打开文件夹
41 |    *
42 |    * @param dir 文件夹
43 |    */
44 |   public static void openDir(File dir) {
45 |     if (!dir.exists()) {
46 |       Toast.error("文件夹不存在");
47 |     }
48 |     try {
49 |       Desktop.getDesktop().open(dir);
50 |     } catch (IOException e) {
51 |       log.error("文件夹打开失败:{}", dir, e);
52 |       throw new IORuntimeException(e);
53 |     }
54 |   }
55 | 
56 |   /**
57 |    * 复制到剪贴板 , 须在FX线程调用
58 |    *
59 |    * @param text 要复制的文字
60 |    */
61 |   public static void copy(String text) {
62 |     Clipboard cb = Clipboard.getSystemClipboard();
63 |     ClipboardContent content = new ClipboardContent();
64 |     content.put(DataFormat.PLAIN_TEXT, text);
65 |     cb.setContent(content);
66 |   }
67 | }
68 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/util/EventUtils.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.util;
 2 | 
 3 | import java.util.function.Consumer;
 4 | import javafx.scene.Node;
 5 | import javafx.scene.input.MouseButton;
 6 | import javafx.scene.input.MouseEvent;
 7 | import lombok.experimental.UtilityClass;
 8 | 
 9 | /**
10 |  * @author blog.unclezs.com
11 |  * @since 2021/4/24 20:35
12 |  */
13 | @UtilityClass
14 | public class EventUtils {
15 | 
16 |   public static final int DOUBLE_CLICK_COUNT = 2;
17 | 
18 |   public static void setOnMousePrimaryClick(Node node, Consumer<MouseEvent> handler) {
19 |     node.setOnMouseClicked(event -> {
20 |       if (event.getButton() == MouseButton.PRIMARY) {
21 |         handler.accept(event);
22 |       }
23 |     });
24 |   }
25 | 
26 |   public static void setOnMouseDoubleClick(Node node, Consumer<MouseEvent> handler) {
27 |     node.setOnMouseClicked(event -> {
28 |       if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == DOUBLE_CLICK_COUNT) {
29 |         handler.accept(event);
30 |       }
31 |     });
32 |   }
33 | }
34 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/util/FontUtils.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.util;
 2 | 
 3 | import javafx.scene.text.Font;
 4 | import lombok.experimental.UtilityClass;
 5 | 
 6 | import java.awt.GraphicsEnvironment;
 7 | import java.util.ArrayList;
 8 | import java.util.Arrays;
 9 | import java.util.Collections;
10 | import java.util.HashSet;
11 | import java.util.List;
12 | import java.util.Set;
13 | 
14 | /**
15 |  * @author blog.unclezs.com
16 |  * @since 2021/7/16 8:37
17 |  */
18 | @UtilityClass
19 | public class FontUtils {
20 |   /**
21 |    * 获取系统的所有字体
22 |    */
23 |   public List<String> getAllFontFamilies() {
24 |     Set<String> fonts = new HashSet<>();
25 |     fonts.addAll(Font.getFamilies());
26 |     fonts.addAll(Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()));
27 |     ArrayList<String> list = new ArrayList<>(fonts);
28 |     Collections.sort(list);
29 |     return list;
30 |   }
31 | }
32 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/util/PlatformUtils.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.util;
 2 | 
 3 | /**
 4 |  * 操作系统工具
 5 |  *
 6 |  * @author blog.unclezs.com
 7 |  * @since 2021/04/03 17:30
 8 |  */
 9 | public class PlatformUtils {
10 | 
11 |   private static final String OS = System.getProperty("os.name");
12 | 
13 |   private static final boolean MAC = OS.startsWith("Mac");
14 |   private static final boolean WINDOWS = OS.startsWith("Windows");
15 |   private static final boolean LINUX = OS.startsWith("Linux");
16 | 
17 |   /**
18 |    * 是否为windows
19 |    *
20 |    * @return true 是
21 |    */
22 |   public static boolean isWindows() {
23 |     return WINDOWS;
24 |   }
25 | 
26 |   /**
27 |    * 是否为Mac
28 |    *
29 |    * @return true 是
30 |    */
31 |   public static boolean isMac() {
32 |     return MAC;
33 |   }
34 | 
35 |   /**
36 |    * 是否为linux
37 |    *
38 |    * @return true 是
39 |    */
40 |   public static boolean isLinux() {
41 |     return LINUX;
42 |   }
43 | }
44 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/com/unclezs/novel/app/framework/util/ToolTipUtil.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.framework.util;
 2 | 
 3 | import java.lang.reflect.Constructor;
 4 | import java.lang.reflect.Field;
 5 | import javafx.scene.control.Tooltip;
 6 | import javafx.util.Duration;
 7 | 
 8 | /**
 9 |  * @author blog.unclezs.com
10 |  * @since 2020/4/30 22:39
11 |  */
12 | @SuppressWarnings("unchecked")
13 | public class ToolTipUtil {
14 | 
15 |   /**
16 |    * 设置显示时间 为立即显示
17 |    */
18 |   public static void init() {
19 |     Class tipClass = Tooltip.class;
20 |     try {
21 |       Field f = tipClass.getDeclaredField("BEHAVIOR");
22 |       f.setAccessible(true);
23 |       Class behavior = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
24 |       Constructor constructor = behavior.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
25 |       constructor.setAccessible(true);
26 |       f.set(behavior, constructor.newInstance(new Duration(0), new Duration(5000), new Duration(100), false));
27 |     } catch (Exception e) {
28 |       e.printStackTrace();
29 |     }
30 |   }
31 | }
32 | 


--------------------------------------------------------------------------------
/app-framework/src/main/java/module-info.java:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * @author blog.unclezs.com
 3 |  * @since 2021/04/03 17:27
 4 |  */
 5 | open module com.unclezs.novel.app.framework {
 6 |   // core
 7 |   requires novel.analyzer;
 8 |   // openjfx
 9 |   requires transitive javafx.graphics;
10 |   requires transitive javafx.controls;
11 |   requires transitive javafx.fxml;
12 |   requires transitive java.desktop;
13 |   requires transitive com.jfoenix;
14 | 
15 |   requires static lombok;
16 |   requires hutool.cache;
17 |   requires hutool.core;
18 |   requires jkeymaster;
19 |   requires com.google.gson;
20 |   requires org.slf4j;
21 | 
22 |   exports com.unclezs.novel.app.framework.core;
23 |   exports com.unclezs.novel.app.framework.components;
24 |   exports com.unclezs.novel.app.framework.components.icon;
25 |   exports com.unclezs.novel.app.framework.components.sidebar;
26 |   exports com.unclezs.novel.app.framework.util;
27 |   exports com.unclezs.novel.app.framework.executor;
28 |   exports com.unclezs.novel.app.framework.appication;
29 |   exports com.unclezs.novel.app.framework.annotation;
30 |   exports com.unclezs.novel.app.framework.animation;
31 |   exports com.unclezs.novel.app.framework.collection;
32 |   exports com.unclezs.novel.app.framework.exception;
33 |   exports com.unclezs.novel.app.framework.serialize;
34 |   exports com.unclezs.novel.app.framework.support;
35 |   exports com.unclezs.novel.app.framework.support.fonts;
36 |   exports com.unclezs.novel.app.framework.support.hotkey;
37 |   exports com.unclezs.novel.app.framework.components.cell;
38 | }
39 | 


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/application.scss:
--------------------------------------------------------------------------------
 1 | /*
 2 |   @import
 3 |   @font-face
 4 |   必需放前面:@see javafx.css.CssParser#parse
 5 | */
 6 | @import "define/_global";
 7 | 
 8 | .root {
 9 |   //-fx-font-family: ".PingFang SC";
10 | }
11 | 
12 | @import "widgets/sidebar-navigation";
13 | @import "widgets/stage-decorator";
14 | @import "widgets/modal";
15 | @import "widgets/loading";
16 | @import "widgets/search-bar";
17 | @import "widgets/scroll-pane";
18 | @import "widgets/popup";
19 | @import "widgets/check-bok";
20 | @import "widgets/list-view";
21 | @import "widgets/spinner";
22 | @import "widgets/tag";
23 | @import "widgets/table-view";
24 | @import "widgets/toast";
25 | @import "widgets/context-menu";
26 | @import "widgets/text-field";
27 | @import "widgets/combo-box";
28 | @import "widgets/input-box";
29 | @import "widgets/loading-image-view";
30 | @import "widgets/hyperlink";
31 | @import "widgets/title-bar";
32 | @import "widgets/tab-button";
33 | @import "widgets/progress-bar";
34 | @import "widgets/tooltip";
35 | @import "widgets/slider";
36 | @import "widgets/tab-pane";
37 | @import "widgets/jfonix";


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/define/_global.scss:
--------------------------------------------------------------------------------
 1 | @import "_variables";
 2 | @import "_colors";
 3 | 
 4 | @mixin shadow {
 5 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 20, 0.19, 0, 6);
 6 | }
 7 | 
 8 | @mixin shadow-1() {
 9 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
10 | }
11 | 
12 | @mixin shadow-2() {
13 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 15, 0.16, 0, 4);
14 | }
15 | 
16 | @mixin shadow-3() {
17 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 20, 0.16, 0, 6);
18 | }
19 | 
20 | @mixin shadow-4() {
21 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 25, 0.25, 0, 8);
22 | }
23 | 
24 | @mixin shadow-5() {
25 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 30, 0.30, 0, 10);
26 | }
27 | 
28 | // shadow-bottom-right radius=3
29 | @mixin shadow-br-3($color) {
30 |   -fx-effect: dropshadow(three-pass-box, $color, 3, 0, 1, 1) !important;
31 | }
32 | 
33 | @mixin shadow-br-1($color) {
34 |   -fx-effect: dropshadow(three-pass-box, $color, 1, 0, 1, 1) !important;
35 | }
36 | 
37 | // 稍微暗点的 字体自适应背景
38 | @mixin ladderTextFill($bgColor) {
39 |   -fx-text-fill: ladder($bgColor, $white_color 59%, $black_color 60%);
40 | }
41 | 
42 | // 纯色黑白 字体自适应背景
43 | @mixin ladderTextFillLight($bgColor) {
44 |   -fx-text-fill: ladder($bgColor, white 59%, black 60%);
45 | }
46 | 
47 | @mixin ladderTextFillForce($bgColor) {
48 |   -fx-text-fill: ladder($bgColor, $white_color 59%, $black_color 60%) !important;
49 | }
50 | 
51 | // 背景色
52 | @mixin ladderBgColor($bgColor) {
53 |   -fx-background-color: ladder($bgColor, $white_color 59%, $black_color 60%);
54 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/define/_variables.scss:
--------------------------------------------------------------------------------
1 | $stage-border-radius: 5
2 | 


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_check-bok.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | .check-box {
 4 |   -fx-checked-color: #009688;
 5 |   -fx-unchecked-color: #888;
 6 | 
 7 |   &:selected .box {
 8 |     -fx-border-color: -fx-unchecked-color;
 9 |   }
10 | 
11 |   .box {
12 |     -fx-pref-width: 14px;
13 |     -fx-pref-height: 14px;
14 |     -fx-border-width: 1px;
15 |   }
16 | 
17 |   .mark {
18 |     -fx-shape: "M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z";
19 |     -fx-max-width: 10px;
20 |     -fx-max-height: 8px;
21 |   }
22 | }
23 | 
24 | .jfx-check-box {
25 |   -jfx-checked-color: -fx-checked-color;
26 |   -jfx-unchecked-color: -fx-unchecked-color;
27 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_combo-box.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | .combo-box {
 4 |   -fx-selected-color: #009688;
 5 | 
 6 |   & > .list-cell {
 7 |     @include ladderTextFill(-fx-base);
 8 |     -fx-alignment: center;
 9 |     -fx-padding: 1px 0 1px 0;
10 |   }
11 | 
12 |   .list-view {
13 |     -fx-background-color: -fx-base;
14 | 
15 |     .list-cell {
16 |       -fx-background-color: -fx-base;
17 |       @include ladderTextFill(-fx-base);
18 | 
19 |       &:hover, &:selected {
20 |         -fx-background-color: -fx-selected-color;
21 |         @include ladderTextFill(-fx-selected-color);
22 |       }
23 |     }
24 |   }
25 | 
26 | }
27 | 
28 | .combo-box-base {
29 | 
30 |   -fx-bg-color: -fx-base;
31 |   -fx-selected-color: #009688;
32 |   -fx-background-color: -fx-bg-color;
33 |   -fx-min-height: 25px;
34 |   @include ladderTextFill(-fx-base);
35 | 
36 |   .arrow-button {
37 |     -fx-background-color: transparent;
38 | 
39 |     .arrow {
40 |       @include ladderTextFill(-fx-base);
41 |     }
42 |   }
43 | 
44 |   &:editable {
45 |     -fx-min-height: 28px;
46 |     -fx-background-color: transparent;
47 | 
48 |     & > .arrow-button {
49 |       -fx-background-color: -fx-bg-color;
50 |     }
51 |   }
52 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_context-menu.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | .context-menu {
 4 |   @include shadow();
 5 |   -fx-selected-color: -fx-base;
 6 |   -fx-background-color: -fx-base !important;
 7 | 
 8 |   .icon, .label {
 9 |     @include ladderTextFill(-fx-base);
10 |   }
11 | 
12 |   .menu-item:hover, .menu-item:focused {
13 |     -fx-background-color: -fx-selected-color;
14 | 
15 |     .icon, .label {
16 |       @include ladderTextFillForce(-fx-selected-color);
17 |     }
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_hyperlink.scss:
--------------------------------------------------------------------------------
1 | .hyperlink {
2 |   -fx-border-color: transparent;
3 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_input-box.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | // 圆角大小
 4 | $radius: 3px;
 5 | 
 6 | .input-box {
 7 |   -fx-bg-color: -fx-base;
 8 | 
 9 |   .text-field {
10 |     -fx-border-radius: $radius 0 0 $radius;
11 |     -fx-background-radius: $radius 0 0 $radius;
12 |     -fx-border-color: transparent;
13 |     -fx-border-width: 1px 0 1px 0;
14 |   }
15 | 
16 |   .icon-button {
17 |     -fx-border-radius: 0 $radius $radius 0;
18 |     -fx-background-radius: 0 $radius $radius 0;
19 |     -fx-border-width: 1px 1px 1px 0;
20 |     -fx-background-color: -fx-bg-color;
21 |     -fx-border-color: transparent;
22 | 
23 |     .icon {
24 |       @include ladderTextFill(-fx-base);
25 |     }
26 |   }
27 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_list-view.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | .list-view {
 4 |   -fx-selected-color: #009688;
 5 |   -fx-separator-color: #bbb;
 6 |   -fx-background-color: -fx-base;
 7 |   -fx-background-insets: 0;
 8 | 
 9 |   &:focused {
10 |     -fx-background-color: -fx-base;
11 |   }
12 | 
13 |   .list-cell {
14 |     @include ladderTextFill(-fx-base);
15 |     -fx-background-color: transparent;
16 |     -fx-border-width: 0 0 1px 0;
17 |     -fx-border-color: -fx-separator-color;
18 | 
19 |     .label, .check-box {
20 |       @include ladderTextFill(-fx-base);
21 |     }
22 | 
23 |     &:empty {
24 |       -fx-background-color: transparent;
25 |       -fx-border-color: transparent;
26 |       -fx-border-width: 0;
27 |     }
28 | 
29 |     &:selected {
30 |       @include ladderTextFill(-fx-selected-color);
31 |       -fx-background-color: -fx-selected-color;
32 | 
33 |       .label, .check-box, .icon {
34 |         @include ladderTextFillForce(-fx-selected-color);
35 |       }
36 | 
37 |       .check-box {
38 |         .box {
39 |           -fx-border-color: ladder(-fx-selected-color, $white_color 49%, $black_color 50%);
40 |         }
41 | 
42 |         &:selected .box {
43 |           -fx-border-color: transparent;
44 |         }
45 |       }
46 |     }
47 |   }
48 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_loading-image-view.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | .loading-image-view {
 4 |   @include shadow();
 5 |   -fx-bg-color: -fx-base;
 6 |   -fx-background-color: transparent;
 7 |   -fx-padding: 0;
 8 |   -fx-background-insets: 0;
 9 | 
10 |   &.loading {
11 |     -fx-background-color: -fx-bg-color;
12 |   }
13 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_loading.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/global";
 2 | 
 3 | .loading {
 4 |   -fx-bg-color: -fx-base;
 5 | 
 6 |   @include shadow;
 7 |   -fx-pref-width: 65px;
 8 |   -fx-max-width: 70px;
 9 |   -fx-max-height: 90px;
10 |   -fx-alignment: center;
11 |   -fx-spacing: 15px;
12 |   -fx-padding: 10px 0 10px 0;
13 |   -fx-background-color: -fx-bg-color;
14 | 
15 |   .jfx-spinner {
16 |     -jfx-radius: 13px;
17 |   }
18 | 
19 |   .jfx-button {
20 |     @include ladderTextFill(-fx-bg-color);
21 |     -jfx-disable-visual-focus: true;
22 |     -fx-text-alignment: center;
23 |   }
24 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_modal.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/global";
 2 | 
 3 | .jfx-alert-overlay {
 4 |   -fx-bg-color: -fx-base;
 5 |   -fx-background-color: transparent;
 6 | 
 7 |   .jfx-alert-content-container {
 8 |     -fx-background-color: transparent;
 9 |     -fx-padding: 20px;
10 | 
11 |     .jfx-dialog-layout {
12 |       @include shadow;
13 |       -fx-background-color: -fx-bg-color;
14 |       -fx-border-color: transparent;
15 | 
16 |       .jfx-layout-body .label {
17 |         -fx-font-size: 12px;
18 |       }
19 | 
20 |       &.info, &.success, &.error, &.warn, &.confirm {
21 |         .jfx-layout-body .label {
22 |           -fx-font-size: 16px;
23 |         }
24 |       }
25 | 
26 |       .jfx-layout-heading .label {
27 |         -fx-border-width: 0 0 1 0;
28 |         -fx-border-color: derive(-fx-bg-color, -50%);
29 |       }
30 | 
31 |       .label, .button, text-field {
32 |         @include ladderTextFill(-fx-bg-color);
33 |       }
34 | 
35 |       .icon {
36 |         -fx-icon-size: 15px;
37 |       }
38 | 
39 |       &.warn .message-icon {
40 |         -fx-text-fill: $md_yellow_900;
41 |       }
42 | 
43 |       &.info .message-icon {
44 |         -fx-text-fill: $md_grey_500;
45 |       }
46 | 
47 |       &.error .message-icon {
48 |         -fx-text-fill: $md_red_800;
49 |       }
50 | 
51 |       &.success .message-icon {
52 |         -fx-text-fill: $md_green_600;
53 |       }
54 | 
55 |       &.confirm .message-icon {
56 |         -fx-text-fill: $md_blue_600;
57 |       }
58 |     }
59 |   }
60 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_popup.scss:
--------------------------------------------------------------------------------
1 | .jfx-popup-container {
2 |   -fx-background-color: -fx-base;
3 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_progress-bar.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | .progress-bar {
 4 |   -fx-color: #009688;
 5 |   -fx-bg-color: #888;
 6 |   -fx-min-height: 5;
 7 |   -fx-indeterminate-bar-length: 100;
 8 |   -fx-indeterminate-bar-animation-time: 2;
 9 | 
10 |   .track {
11 |     -fx-background-color: theme-color-light;
12 |     -fx-background-insets: 0, 0 0 1 0, 1 1 2 1;
13 |   }
14 | 
15 |   .bar {
16 |     -fx-background-color: theme-color;
17 |     -fx-background-insets: 0;
18 |     -fx-padding: 3;
19 |   }
20 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_scroll-pane.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/global";
 2 | 
 3 | 
 4 | .scroll-pane {
 5 |   -fx-background-color: transparent;
 6 | 
 7 |   .viewport {
 8 |     -fx-background-color: transparent;
 9 |   }
10 | }
11 | 
12 | .scroll-bar {
13 |   // 滚动条颜色
14 |   -fx-color: -fx-base;
15 |   // 滚动条hover颜色
16 |   -fx-hover-color: #888;
17 | 
18 |   -fx-pref-width: 10px;
19 |   -fx-background-color: transparent;
20 | 
21 |   .track {
22 |     -fx-background-color: transparent;
23 |   }
24 | 
25 |   .track-background {
26 |     -fx-background-color: transparent;
27 |   }
28 | 
29 |   .thumb {
30 |     -fx-background-color: -fx-color;
31 | 
32 |     &:hover, &:pressed {
33 |       -fx-background-color: -fx-hover-color;
34 |     }
35 |   }
36 | 
37 |   .increment-button {
38 |     -fx-background-color: transparent;
39 |     -fx-max-height: 0;
40 |   }
41 | 
42 |   .increment-arrow {
43 |     -fx-background-color: transparent;
44 |     -fx-max-height: 0;
45 |   }
46 | 
47 |   .decrement-button {
48 |     -fx-background-color: transparent;
49 |     -fx-max-height: 0;
50 |   }
51 | 
52 |   .decrement-arrow {
53 |     -fx-background-color: transparent;
54 |     -fx-max-height: 0;
55 |   }
56 | }
57 | 


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_search-bar.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | // 圆角大小
 4 | $radius: 3px;
 5 | 
 6 | .search-bar {
 7 |   -fx-bg-color: -fx-base;
 8 | 
 9 |   .combo-box-base {
10 |     -fx-border-radius: $radius 0 0 $radius;
11 |     -fx-background-radius: $radius 0 0 $radius;
12 |     -fx-background-color: -fx-bg-color;
13 |     -fx-background-insets: 0;
14 |     -fx-border-width: 0;
15 |     -fx-padding: 0.333333em 0.666667em 0.333333em 0.666667em;
16 | 
17 |     .arrow-button {
18 |       -fx-padding: 0;
19 |     }
20 |   }
21 | 
22 |   .text-field {
23 |     @include ladderTextFill(-fx-base);
24 |     -fx-background-color: -fx-bg-color;
25 |     -fx-border-color: transparent;
26 |     -fx-border-radius: 0;
27 |     -fx-background-radius: 0;
28 |     -fx-border-width: 1px 0 1px 0;
29 |   }
30 | 
31 |   .icon-button {
32 |     -fx-border-radius: 0 $radius $radius 0;
33 |     -fx-background-radius: 0 $radius $radius 0;
34 |     -fx-border-width: 1px 1px 1px 0;
35 |     -fx-background-color: -fx-bg-color;
36 |     -fx-border-color: transparent;
37 | 
38 |     .icon {
39 |       @include ladderTextFill(-fx-base);
40 |     }
41 |   }
42 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_slider.scss:
--------------------------------------------------------------------------------
1 | @import "../define/_global";
2 | 
3 | .jfx-slider {
4 |   .slider-value {
5 |     -fx-stroke: ladder(-jfx-default-thumb, white 59%, #2d2a2a 60%);
6 |   }
7 | }
8 | 


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_spinner.scss:
--------------------------------------------------------------------------------
 1 | .spinner {
 2 |   -fx-bg-color: #888;
 3 |   -fx-background-color: -fx-bg-color;
 4 | 
 5 |   .text-field {
 6 |     -fx-background-color: transparent;
 7 |     -fx-alignment: center;
 8 |   }
 9 | 
10 |   .decrement-arrow-button, .increment-arrow-button {
11 |     -fx-background-color: transparent;
12 |   }
13 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_tab-button.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | .tab-button {
 4 |   // 选中颜色
 5 |   -fx-selected-color: #009688;
 6 |   // 默认颜色
 7 |   -fx-color: rgb(228, 232, 225);
 8 |   -fx-border-color: transparent;
 9 |   -fx-border-radius: 0;
10 |   -fx-background-radius: 0;
11 |   -fx-background-color: -fx-selected-color;
12 |   -fx-opacity: 0.5;
13 |   @include ladderTextFill(-fx-selected-color);
14 | 
15 |   .icon {
16 |     @include ladderTextFill(-fx-selected-color);
17 |   }
18 | 
19 |   &:selected {
20 |     -fx-opacity: 1;
21 |   }
22 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_tab-pane.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | 
 4 | .jfx-tab-pane {
 5 |   -fx-color: #000;
 6 | 
 7 |   .tab-header-background {
 8 |     -fx-background-color: -fx-base;
 9 |   }
10 | 
11 |   .headers-region {
12 |     .tab {
13 |       & > .jfx-rippler {
14 |         -jfx-rippler-fill: -fx-color;
15 |       }
16 | 
17 |       &:selected .tab-container .tab-label {
18 |         @include ladderTextFill(-fx-base);
19 |       }
20 |     }
21 | 
22 |     .tab-container .tab-label {
23 |       @include ladderTextFill(-fx-base);
24 |       -fx-font-weight: BOLD;
25 |       -fx-padding: 3 10 3 10;
26 |       -fx-font-size: 13;
27 |     }
28 | 
29 |     .tab-selected-line {
30 |       -fx-background-color: -fx-color;;
31 |     }
32 |   }
33 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_tag.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | .tag {
 4 |   -fx-tag-color: #009688;
 5 |   @include ladderTextFillForce(-fx-tag-color);
 6 |   -fx-background-color: -fx-tag-color;
 7 |   -fx-padding: 1px 3px 1px 3px;
 8 |   -fx-background-radius: 3px;
 9 |   -fx-border-radius: 3px;
10 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_text-field.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/global";
 2 | 
 3 | .text-field, .text-area {
 4 |   -fx-bg-color: -fx-base;
 5 |   @include ladderTextFill(-fx-base);
 6 |   -fx-background-color: -fx-bg-color;
 7 |   -fx-border-color: transparent;
 8 | 
 9 |   * {
10 |     -fx-background-color: transparent !important;
11 |   }
12 | 
13 |   .context-menu {
14 |     -fx-background-color: -fx-base !important;
15 | 
16 |     .icon, .label {
17 |       @include ladderTextFillForce(-fx-base);
18 |     }
19 | 
20 |     .menu-item:hover, .menu-item:focused {
21 |       -fx-background-color: -fx-selected-color !important;
22 | 
23 |       .icon, .label {
24 |         @include ladderTextFillForce(-fx-selected-color);
25 |       }
26 |     }
27 |   }
28 | }
29 | 
30 | .text-area > .scroll-pane > .scroll-bar:vertical {
31 |   // 滚动条hover颜色
32 |   -fx-hover-color: #888;
33 | 
34 |   .thumb {
35 |     -fx-background-color: -fx-color !important;
36 | 
37 |     &:hover, &:pressed {
38 |       -fx-background-color: -fx-hover-color !important;
39 |     }
40 |   }
41 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_title-bar.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/global";
 2 | 
 3 | .title-bar {
 4 |   -fx-action-color: #009688;
 5 |   -fx-padding: 10px 10px 0 10px !important;
 6 |   -fx-alignment: center;
 7 | 
 8 |   .title {
 9 |     -fx-padding: -2px 0 0 0;
10 |     @include ladderTextFill(-fx-base);
11 |     -fx-font-size: 20px;
12 |   }
13 | 
14 |   .content {
15 |   }
16 | 
17 |   .actions {
18 |     -fx-alignment: center;
19 |     @include shadow-br-3(-fx-action-color);
20 |     -fx-spacing: 5;
21 |     -fx-background-color: -fx-action-color;
22 |     -fx-border-radius: 10;
23 |     -fx-background-radius: 10;
24 | 
25 |     .button {
26 |       -fx-font-size: 13px;
27 |       @include ladderTextFill(-fx-action-color);
28 | 
29 |       .icon {
30 |         -fx-icon-size: 11px;
31 |         -fx-text-fill: inherit;
32 |       }
33 |     }
34 |   }
35 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_toast.scss:
--------------------------------------------------------------------------------
 1 | @import "../define/_global";
 2 | 
 3 | .toast {
 4 | 
 5 |   .box {
 6 |     -fx-spacing: 10px;
 7 |     -fx-alignment: BOTTOM_CENTER;
 8 |     -fx-max-height: 120px;
 9 |   }
10 | 
11 |   .label {
12 |     @include shadow();
13 |     -fx-padding: 5px 10px 5px 10px;
14 |     -fx-background-radius: 5px;
15 |     -fx-border-radius: 5px;
16 |     -fx-font-size: 12px !important;
17 |   }
18 | 
19 |   .icon {
20 |     -fx-text-fill: inherit;
21 |   }
22 | 
23 |   .warn {
24 |     -fx-background-color: rgb(253, 246, 236) !important;
25 |     -fx-text-fill: rgb(230, 162, 60) !important;
26 |   }
27 | 
28 |   .info {
29 |     -fx-background-color: rgb(237, 242, 252) !important;
30 |     -fx-text-fill: rgb(144, 147, 153) !important;
31 |   }
32 | 
33 |   .success {
34 |     -fx-text-fill: rgb(103, 194, 58) !important;
35 |     -fx-background-color: rgb(240, 249, 235) !important;
36 |   }
37 | 
38 |   .error {
39 |     -fx-background-color: rgb(254, 240, 240) !important;
40 |     -fx-text-fill: rgb(245, 108, 108) !important;
41 |   }
42 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/_tooltip.scss:
--------------------------------------------------------------------------------
1 | @import "../define/_global";
2 | 
3 | .tooltip {
4 |   @include shadow();
5 |   @include ladderTextFill(-fx-base);
6 |   -fx-background-color: -fx-base;
7 |   -fx-font-size: 12px;
8 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/jfonix.css:
--------------------------------------------------------------------------------
1 | .disable-visual-focus {
2 |   -jfx-disable-visual-focus: true;
3 | }
4 | 


--------------------------------------------------------------------------------
/app-framework/src/main/resources/css/widgets/jfonix.scss:
--------------------------------------------------------------------------------
1 | .disable-visual-focus {
2 |   -jfx-disable-visual-focus: true;
3 | }


--------------------------------------------------------------------------------
/app-framework/src/main/resources/fonts/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app-framework/src/main/resources/fonts/iconfont.ttf


--------------------------------------------------------------------------------
/app-framework/src/main/resources/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app-framework/src/main/resources/images/loading.gif


--------------------------------------------------------------------------------
/app-localized/app-localized.gradle:
--------------------------------------------------------------------------------
1 | import com.unclezs.novel.app.localized.TranslateTask
2 | 
3 | // 利用百度翻译Api自动国际化
4 | task localized(type: TranslateTask, group: "common") {
5 |     appId = findProperty("baidu.translate.appId")
6 |     secret = findProperty("baidu.translate.secret")
7 | }


--------------------------------------------------------------------------------
/app-localized/src/main/resources/com/unclezs/novel/app/localized/app/home.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app-localized/src/main/resources/com/unclezs/novel/app/localized/app/home.properties


--------------------------------------------------------------------------------
/app-localized/src/main/resources/com/unclezs/novel/app/localized/widgets/common.properties:
--------------------------------------------------------------------------------
1 | search=\u641C\u7D22
2 | search.prompt=\u8BF7\u8F38\u5165\u641C\u7D22\u5167\u5BB9~
3 | 


--------------------------------------------------------------------------------
/app-localized/src/main/resources/com/unclezs/novel/app/localized/widgets/common_en.properties:
--------------------------------------------------------------------------------
1 | #Generate By Baidu Translate
2 | #Sun Jul 11 17:12:49 CST 2021
3 | search=search
4 | search.prompt=search for something~
5 | 


--------------------------------------------------------------------------------
/app-localized/src/main/resources/com/unclezs/novel/app/localized/widgets/common_zh_TW.properties:
--------------------------------------------------------------------------------
1 | #Generate By Baidu Translate
2 | #Sun Jul 11 17:12:49 CST 2021
3 | search=\u8490\u7D22
4 | search.prompt=\u8ACB\u8F38\u5165\u8490\u7D22\u5167\u5BB9~
5 | 


--------------------------------------------------------------------------------
/app-localized/src/main/resources/com/unclezs/novel/app/localized/widgets/modal.properties:
--------------------------------------------------------------------------------
1 | cancel=\u53D6\u6D88
2 | submit=\u786E\u5B9A
3 | info=\u63D0\u793A
4 | success=\u6210\u529F
5 | error=\u9519\u8BEF
6 | warn=\u8B66\u544A
7 | input.prompt=\u8BF7\u8F93\u5165
8 | confirm=\u8BF7\u786E\u8BA4


--------------------------------------------------------------------------------
/app-localized/src/main/resources/com/unclezs/novel/app/localized/widgets/modal_en.properties:
--------------------------------------------------------------------------------
 1 | #Generate By Baidu Translate
 2 | #Sun Jul 11 17:12:49 CST 2021
 3 | cancel=cancel
 4 | confirm=Please confirm
 5 | warn=warning
 6 | input.prompt=Please input
 7 | submit=determine
 8 | success=success
 9 | error=error
10 | info=Tips
11 | 


--------------------------------------------------------------------------------
/app-localized/src/main/resources/com/unclezs/novel/app/localized/widgets/modal_zh_TW.properties:
--------------------------------------------------------------------------------
 1 | #Generate By Baidu Translate
 2 | #Sun Jul 11 17:12:49 CST 2021
 3 | cancel=\u53D6\u6D88
 4 | confirm=\u8ACB\u78BA\u8A8D
 5 | warn=\u8B66\u544A
 6 | input.prompt=\u8ACB\u8F38\u5165
 7 | submit=\u78BA\u5B9A
 8 | success=\u6210\u529F
 9 | error=\u932F\u8AA4
10 | info=\u63D0\u793A
11 | 


--------------------------------------------------------------------------------
/app-localized/src/main/resources/com/unclezs/novel/app/localized/widgets/stage-decorator.properties:
--------------------------------------------------------------------------------
1 | decorator.exit=\u9000\u51FA
2 | decorator.max=\u6700\u5927\u5316
3 | decorator.min=\u6700\u5C0F\u5316
4 | decorator.restore=\u8FD8\u539F
5 | decorator.theme=\u6362\u80A4
6 | decorator.setting=\u8BBE\u7F6E


--------------------------------------------------------------------------------
/app-localized/src/main/resources/com/unclezs/novel/app/localized/widgets/stage-decorator_en.properties:
--------------------------------------------------------------------------------
1 | #Generate By Baidu Translate
2 | #Sun Jul 11 17:12:49 CST 2021
3 | decorator.max=Maximize
4 | decorator.setting=set up
5 | decorator.theme=skin peeler
6 | decorator.min=minimize
7 | decorator.restore=reduction
8 | decorator.exit=sign out
9 | 


--------------------------------------------------------------------------------
/app-localized/src/main/resources/com/unclezs/novel/app/localized/widgets/stage-decorator_zh_TW.properties:
--------------------------------------------------------------------------------
1 | #Generate By Baidu Translate
2 | #Sun Jul 11 17:12:49 CST 2021
3 | decorator.max=\u6700\u5927\u5316
4 | decorator.setting=\u8A2D\u5B9A
5 | decorator.theme=\u63DB\u819A
6 | decorator.min=\u6700\u5C0F\u5316
7 | decorator.restore=\u9084\u539F
8 | decorator.exit=\u9000\u51FA
9 | 


--------------------------------------------------------------------------------
/app/app.modularity:
--------------------------------------------------------------------------------
1 | --add-exports=javafx.graphics/com.sun.javafx.css=com.unclezs.novel.app.framework
2 | --add-exports=javafx.graphics/com.sun.javafx.stage=com.unclezs.novel.app.main,com.jfoenix
3 | --add-exports=javafx.graphics/com.sun.javafx.scene=com.jfoenix
4 | --add-exports=javafx.base/com.sun.javafx.binding=com.jfoenix
5 | --add-exports=javafx.base/com.sun.javafx.event=com.jfoenix
6 | --add-exports=javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix
7 | --add-exports=javafx.controls/com.sun.javafx.scene.control=com.jfoenix
8 | --add-exports=javafx.controls/com.sun.javafx.scene.control.skin=com.unclezs.novel.app.main
9 | --add-opens=com.jfoenix/com.jfoenix.controls=com.unclezs.novel.app.framework


--------------------------------------------------------------------------------
/app/db/core.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/db/core.db


--------------------------------------------------------------------------------
/app/packager/bin/linux/kindlegen_linux:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/bin/linux/kindlegen_linux


--------------------------------------------------------------------------------
/app/packager/bin/mac/kindlegen:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/bin/mac/kindlegen


--------------------------------------------------------------------------------
/app/packager/bin/win/kindlegen.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/bin/win/kindlegen.exe


--------------------------------------------------------------------------------
/app/packager/fonts/pingfang-simple-bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/fonts/pingfang-simple-bold.ttf


--------------------------------------------------------------------------------
/app/packager/icon/favicon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/icon/favicon.icns


--------------------------------------------------------------------------------
/app/packager/icon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/icon/favicon.ico


--------------------------------------------------------------------------------
/app/packager/icon/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/icon/favicon.png


--------------------------------------------------------------------------------
/app/packager/inno-setup/language/chinese-simple.isl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/inno-setup/language/chinese-simple.isl


--------------------------------------------------------------------------------
/app/packager/resource/conf/core.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/resource/conf/core.db


--------------------------------------------------------------------------------
/app/packager/resource/conf/jul.properties:
--------------------------------------------------------------------------------
1 | handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
2 | java.util.logging.FileHandler.pattern=./logs/log-system.log
3 | java.util.logging.FileHandler.limit=50000
4 | java.util.logging.FileHandler.count=1
5 | java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
6 | java.util.logging.ConsoleHandler.level=INFO
7 | java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
8 | java.util.logging.SimpleFormatter.format=%1$tF %1$tH:%1$tM:%1$tS %2$s%n%4$s: %5$s%6$s%n
9 | com.xyz.foo.level=SEVERE


--------------------------------------------------------------------------------
/app/packager/resource/conf/launcher.vmoptions:
--------------------------------------------------------------------------------
1 | -Djava.net.useSystemProxies=true


--------------------------------------------------------------------------------
/app/packager/sciprt/run.bat:
--------------------------------------------------------------------------------
 1 | .\runtime\bin\java ^
 2 |  --add-exports=javafx.graphics/com.sun.javafx.css=com.unclezs.novel.app.framework ^
 3 |  --add-exports=javafx.graphics/com.sun.javafx.stage=com.unclezs.novel.app.main,com.jfoenix ^
 4 |  --add-exports=javafx.graphics/com.sun.javafx.scene=com.jfoenix ^
 5 |  --add-exports=javafx.base/com.sun.javafx.binding=com.jfoenix ^
 6 |  --add-exports=javafx.base/com.sun.javafx.event=com.jfoenix ^
 7 |  --add-exports=javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix ^
 8 |  --add-exports=javafx.controls/com.sun.javafx.scene.control=com.jfoenix ^
 9 |  --add-exports=javafx.controls/com.sun.javafx.scene.control.skin=com.unclezs.novel.app.main ^
10 |  --add-opens=com.jfoenix/com.jfoenix.controls=com.unclezs.novel.app.framework ^
11 |  --module-path ".\libraries;app.jar" -m com.unclezs.novel.app.main/com.unclezs.novel.app.main.App


--------------------------------------------------------------------------------
/app/packager/screenshot/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/screenshot/home.png


--------------------------------------------------------------------------------
/app/packager/screenshot/read.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/screenshot/read.png


--------------------------------------------------------------------------------
/app/packager/screenshot/read1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/screenshot/read1.png


--------------------------------------------------------------------------------
/app/packager/screenshot/setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/packager/screenshot/setting.png


--------------------------------------------------------------------------------
/app/proguard.pro:
--------------------------------------------------------------------------------
1 | -useuniqueclassmembernames
2 | -dontshrink
3 | -dontnote
4 | -dontwarn
5 | -overloadaggressively
6 | -allowaccessmodification


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/core/ChapterComparator.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.core;
 2 | 
 3 | import com.unclezs.novel.analyzer.util.regex.RegexUtils;
 4 | import com.unclezs.novel.analyzer.util.uri.UrlUtils;
 5 | import com.unclezs.novel.app.main.model.ChapterProperty;
 6 | 
 7 | import java.math.BigInteger;
 8 | import java.util.Comparator;
 9 | 
10 | /**
11 |  * 章节排序比较器
12 |  *
13 |  * @author blog.unclezs.com
14 |  * @since 2020/12/21 17:54
15 |  */
16 | public class ChapterComparator implements Comparator<ChapterProperty> {
17 | 
18 |   @Override
19 |   public int compare(ChapterProperty o1, ChapterProperty o2) {
20 |     String one = UrlUtils.getUrlLastPathNotSuffix(o1.getChapter().getUrl());
21 |     String two = UrlUtils.getUrlLastPathNotSuffix(o2.getChapter().getUrl());
22 |     if (RegexUtils.isNumber(one) && RegexUtils.isNumber(two)) {
23 |       BigInteger v1 = new BigInteger(one);
24 |       BigInteger v2 = new BigInteger(two);
25 |       return v1.compareTo(v2);
26 |     }
27 |     return one.compareTo(two);
28 |   }
29 | }
30 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/core/loader/AbstractBookLoader.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.core.loader;
 2 | 
 3 | import cn.hutool.core.io.FileUtil;
 4 | import com.unclezs.novel.analyzer.core.model.AnalyzerRule;
 5 | import com.unclezs.novel.analyzer.model.Chapter;
 6 | import com.unclezs.novel.app.main.db.beans.Book;
 7 | import com.unclezs.novel.app.main.model.BookCache;
 8 | import com.unclezs.novel.app.main.util.BookHelper;
 9 | import com.unclezs.novel.app.main.views.home.FictionBookshelfView;
10 | import lombok.Getter;
11 | 
12 | import java.util.List;
13 | 
14 | /**
15 |  * 书籍加载器
16 |  *
17 |  * @author blog.unclezs.com
18 |  * @since 2021/5/7 16:00
19 |  */
20 | public abstract class AbstractBookLoader {
21 | 
22 |   @Getter
23 |   protected Book book;
24 |   protected List<Chapter> toc;
25 |   protected AnalyzerRule rule;
26 | 
27 |   public void setBook(Book book) {
28 |     this.book = book;
29 |     BookCache cache = BookHelper.loadCache(FileUtil.file(FictionBookshelfView.CACHE_FOLDER, book.getId()));
30 |     this.toc = cache.getToc();
31 |     this.rule = cache.getRule();
32 |   }
33 | 
34 | 
35 |   /**
36 |    * 返回章节列表
37 |    *
38 |    * @return 章节列表
39 |    */
40 |   public List<Chapter> toc() {
41 |     return toc;
42 |   }
43 | 
44 |   /**
45 |    * 获取正文
46 |    *
47 |    * @param index 章节索引
48 |    * @return 正文
49 |    */
50 |   public String content(int index) {
51 |     if (index >= 0 && index < toc().size()) {
52 |       return loadContent(index);
53 |     }
54 |     return null;
55 |   }
56 | 
57 | 
58 |   /**
59 |    * 获取正文
60 |    *
61 |    * @param index 章节索引
62 |    * @return 正文
63 |    */
64 |   public abstract String loadContent(int index);
65 | 
66 |   /**
67 |    * 正文是否已经被缓存了
68 |    *
69 |    * @param index 索引
70 |    * @return true 已经被缓存
71 |    */
72 |   public boolean isCached(int index) {
73 |     return index < toc.size() && FileUtil.file(FictionBookshelfView.CACHE_FOLDER, book.getId(),
74 |       String.valueOf(index)).exists();
75 |   }
76 | }
77 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/core/pipeline/EbookPipeline.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.core.pipeline;
 2 | 
 3 | import cn.hutool.core.io.FileUtil;
 4 | import com.unclezs.novel.analyzer.core.helper.AnalyzerHelper;
 5 | import com.unclezs.novel.analyzer.model.Chapter;
 6 | import com.unclezs.novel.analyzer.spider.pipline.AbstractTextPipeline;
 7 | import com.unclezs.novel.app.main.util.EbookUtils;
 8 | import lombok.Setter;
 9 | 
10 | import java.io.File;
11 | 
12 | /**
13 |  * epub下载管道
14 |  *
15 |  * @author blog.unclezs.com
16 |  * @since 2021/5/1 15:59
17 |  */
18 | @Setter
19 | public class EbookPipeline extends AbstractTextPipeline {
20 | 
21 |   private boolean mobi;
22 |   private boolean epub;
23 | 
24 |   @Override
25 |   public void process(Chapter chapter) {
26 |     // 预处理文本格式
27 |     chapter.setContent(AnalyzerHelper.formatContent(chapter.getContent()));
28 |     // 生成章节
29 |     EbookUtils.generateChapter(chapter, new File(getFilePath()));
30 | 
31 |   }
32 | 
33 |   @Override
34 |   public void processChapter(Chapter chapter) {
35 |     // do nothing
36 |   }
37 | 
38 |   @Override
39 |   public void onComplete() {
40 |     if (mobi || epub) {
41 |       File outDir = new File(getFilePath());
42 |       File tmpFile = EbookUtils.toEbook(getNovel(), outDir, false);
43 |       if (epub) {
44 |         EbookUtils.toEpub(outDir);
45 |       }
46 |       if (mobi) {
47 |         EbookUtils.toMobi(outDir);
48 |       }
49 |       // 删除临时文件
50 |       FileUtil.del(tmpFile);
51 |     }
52 |   }
53 | }
54 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/db/DataHelper.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.db;
 2 | 
 3 | import cn.hutool.core.io.FileUtil;
 4 | import com.unclezs.novel.app.main.db.dao.AudioBookDao;
 5 | import com.unclezs.novel.app.main.db.dao.BookDao;
 6 | import com.unclezs.novel.app.main.db.dao.DownloadHistoryDao;
 7 | import com.unclezs.novel.app.main.db.dao.SearchEngineDao;
 8 | import com.unclezs.novel.app.main.db.dao.TxtTocRuleDao;
 9 | import lombok.extern.slf4j.Slf4j;
10 | 
11 | import java.sql.SQLException;
12 | 
13 | /**
14 |  * @author blog.unclezs.com
15 |  * @since 2021/6/1 1:04
16 |  */
17 | @Slf4j
18 | public class DataHelper {
19 | 
20 |   public static void main(String[] args) throws SQLException {
21 |     initDb();
22 |   }
23 | 
24 |   /**
25 |    * 重建表与初始数据
26 |    */
27 |   public static void initDb() {
28 |     try {
29 |       FileUtil.mkdir(FileUtil.file("conf"));
30 |       String[] args = {};
31 |       AudioBookDao.main(args);
32 |       BookDao.main(args);
33 |       DownloadHistoryDao.main(args);
34 |       TxtTocRuleDao.main(args);
35 |       SearchEngineDao.main(args);
36 |     } catch (SQLException e) {
37 |       log.error("初始化数据库失败", e);
38 |     }
39 |   }
40 | }
41 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/db/beans/TxtTocRule.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.db.beans;
 2 | 
 3 | import com.j256.ormlite.field.DatabaseField;
 4 | import com.j256.ormlite.table.DatabaseTable;
 5 | import lombok.Data;
 6 | 
 7 | /**
 8 |  * @author blog.unclezs.com
 9 |  * @since 2021/5/10 1:38
10 |  */
11 | @Data
12 | @DatabaseTable(tableName = "txt_toc_rule")
13 | public class TxtTocRule {
14 | 
15 |   @DatabaseField(generatedId = true, allowGeneratedIdInsert = true)
16 |   private int id;
17 |   @DatabaseField
18 |   private String name;
19 |   @DatabaseField
20 |   private String rule;
21 |   @DatabaseField
22 |   private int order;
23 | }
24 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/db/dao/AudioBookDao.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.db.dao;
 2 | 
 3 | import com.j256.ormlite.table.TableUtils;
 4 | import com.unclezs.novel.app.main.db.BaseDao;
 5 | import com.unclezs.novel.app.main.db.beans.AudioBook;
 6 | 
 7 | import java.sql.SQLException;
 8 | 
 9 | /**
10 |  * 有声书 DAO
11 |  *
12 |  * @author blog.unclezs.com
13 |  * @since 2021/5/5 21:00
14 |  */
15 | public class AudioBookDao extends BaseDao<AudioBook> {
16 | 
17 | 
18 |   public static void main(String[] args) throws SQLException {
19 |     AudioBookDao dao = new AudioBookDao();
20 |     TableUtils.dropTable(dao.dao, true);
21 |     TableUtils.createTable(dao.dao);
22 |   }
23 | }
24 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/db/dao/BookDao.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.db.dao;
 2 | 
 3 | import com.j256.ormlite.table.TableUtils;
 4 | import com.unclezs.novel.app.main.db.BaseDao;
 5 | import com.unclezs.novel.app.main.db.beans.Book;
 6 | 
 7 | import java.sql.SQLException;
 8 | 
 9 | /**
10 |  * @author blog.unclezs.com
11 |  * @since 2021/5/7 11:00
12 |  */
13 | public class BookDao extends BaseDao<Book> {
14 | 
15 |   public static void main(String[] args) throws SQLException {
16 |     BookDao dao = new BookDao();
17 |     TableUtils.dropTable(dao.dao, true);
18 |     TableUtils.createTable(dao.dao);
19 |   }
20 | }
21 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/db/dao/DownloadHistoryDao.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.db.dao;
 2 | 
 3 | import com.j256.ormlite.table.TableUtils;
 4 | import com.unclezs.novel.app.main.db.BaseDao;
 5 | import com.unclezs.novel.app.main.db.beans.DownloadHistory;
 6 | import lombok.extern.slf4j.Slf4j;
 7 | 
 8 | import java.sql.SQLException;
 9 | 
10 | /**
11 |  * 下载历史DAO
12 |  *
13 |  * @author blog.unclezs.com
14 |  * @since 2021/5/5 12:20
15 |  */
16 | @Slf4j
17 | public class DownloadHistoryDao extends BaseDao<DownloadHistory> {
18 | 
19 |   public static void main(String[] args) throws SQLException {
20 |     DownloadHistoryDao dao = new DownloadHistoryDao();
21 |     TableUtils.dropTable(dao.dao, true);
22 |     TableUtils.createTable(dao.dao);
23 |   }
24 | }
25 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/db/dao/SearchEngineDao.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.db.dao;
 2 | 
 3 | import com.j256.ormlite.table.TableUtils;
 4 | import com.unclezs.novel.app.main.db.BaseDao;
 5 | import com.unclezs.novel.app.main.db.beans.SearchEngine;
 6 | import javafx.collections.FXCollections;
 7 | import javafx.collections.ObservableList;
 8 | 
 9 | import java.sql.SQLException;
10 | 
11 | /**
12 |  * @author blog.unclezs.com
13 |  * @since 2021/5/7 11:00
14 |  */
15 | public class SearchEngineDao extends BaseDao<SearchEngine> {
16 | 
17 |   private static SearchEngineDao searchEngineDao;
18 |   private ObservableList<SearchEngine> engines;
19 | 
20 |   private SearchEngineDao() {
21 |   }
22 | 
23 |   public static SearchEngineDao me() {
24 |     if (searchEngineDao == null) {
25 |       searchEngineDao = new SearchEngineDao();
26 |     }
27 |     return searchEngineDao;
28 |   }
29 | 
30 | 
31 |   public static void main(String[] args) throws SQLException {
32 |     SearchEngineDao dao = new SearchEngineDao();
33 |     TableUtils.dropTable(dao.dao, true);
34 |     TableUtils.createTable(dao.dao);
35 |     // 默认数据
36 |     dao.dao.create(SearchEngine.getDefault());
37 |   }
38 | 
39 |   public ObservableList<SearchEngine> all() {
40 |     if (engines == null) {
41 |       engines = FXCollections.observableArrayList(selectAll());
42 |     }
43 |     return engines;
44 |   }
45 | }
46 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/db/dao/TxtTocRuleDao.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.db.dao;
 2 | 
 3 | import cn.hutool.core.io.IoUtil;
 4 | import com.google.gson.reflect.TypeToken;
 5 | import com.j256.ormlite.table.TableUtils;
 6 | import com.unclezs.novel.analyzer.util.GsonUtils;
 7 | import com.unclezs.novel.app.framework.util.ResourceUtils;
 8 | import com.unclezs.novel.app.main.db.BaseDao;
 9 | import com.unclezs.novel.app.main.db.beans.TxtTocRule;
10 | import lombok.extern.slf4j.Slf4j;
11 | 
12 | import java.sql.SQLException;
13 | import java.util.Collections;
14 | import java.util.List;
15 | import java.util.stream.Collectors;
16 | 
17 | /**
18 |  * @author blog.unclezs.com
19 |  * @since 2021/5/10 2:04
20 |  */
21 | @Slf4j
22 | public class TxtTocRuleDao extends BaseDao<TxtTocRule> {
23 | 
24 |   public static final String PATH = "assets/defaults/toc-rule.json";
25 | 
26 |   public static void main(String[] args) throws SQLException {
27 |     TxtTocRuleDao dao = new TxtTocRuleDao();
28 |     TableUtils.dropTable(dao.dao, true);
29 |     TableUtils.createTable(dao.dao);
30 |     dao.importDefault();
31 |   }
32 | 
33 | 
34 |   /**
35 |    * 导入默认数据
36 |    *
37 |    * @return 默认数据
38 |    */
39 |   public List<TxtTocRule> importDefault() {
40 |     String defaultRuleJson = IoUtil.readUtf8(ResourceUtils.stream(PATH));
41 |     List<TxtTocRule> rules = GsonUtils.me().fromJson(defaultRuleJson, new TypeToken<List<TxtTocRule>>() {
42 |     }.getType());
43 |     try {
44 |       dao.deleteIds(rules.stream().map(TxtTocRule::getId).collect(Collectors.toList()));
45 |       dao.create(rules);
46 |     } catch (SQLException e) {
47 |       log.error("导入默认章节解析规则数据失败");
48 |       e.printStackTrace();
49 |     }
50 |     return rules;
51 |   }
52 | 
53 |   /**
54 |    * 查询所有,按照order正序
55 |    */
56 |   public List<TxtTocRule> selectAllByOrder() {
57 |     try {
58 |       return dao.queryBuilder().orderBy("order", true).query();
59 |     } catch (SQLException e) {
60 |       log.error("查询全部TXT目录规则失败", e);
61 |     }
62 |     return Collections.emptyList();
63 |   }
64 | }
65 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/enums/DownloadType.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.enums;
 2 | 
 3 | /**
 4 |  * 下载类型
 5 |  *
 6 |  * @author blog.unclezs.com
 7 |  * @since 2021/4/30 14:58
 8 |  */
 9 | public enum DownloadType {
10 |   /**
11 |    * 文本
12 |    */
13 |   TEXT,
14 |   /**
15 |    * 有声
16 |    */
17 |   AUDIO
18 | }
19 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/exception/DbException.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.exception;
 2 | 
 3 | /**
 4 |  * DB相关异常
 5 |  *
 6 |  * @author blog.unclezs.com
 7 |  * @since 2021/5/5 12:31
 8 |  */
 9 | public class DbException extends RuntimeException {
10 | 
11 |   public DbException() {
12 |   }
13 | 
14 |   public DbException(String message) {
15 |     super(message);
16 |   }
17 | 
18 |   public DbException(String message, Throwable cause) {
19 |     super(message, cause);
20 |   }
21 | 
22 |   public DbException(Throwable cause) {
23 |     super(cause);
24 |   }
25 | 
26 |   public DbException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
27 |     super(message, cause, enableSuppression, writableStackTrace);
28 |   }
29 | }
30 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/manager/LanguageManager.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.manager;
 2 | 
 3 | import javafx.collections.FXCollections;
 4 | import javafx.collections.ObservableList;
 5 | import lombok.experimental.UtilityClass;
 6 | 
 7 | import java.util.HashMap;
 8 | import java.util.Locale;
 9 | import java.util.Map;
10 | import java.util.Map.Entry;
11 | 
12 | /**
13 |  * 支持的语言
14 |  *
15 |  * @author blog.unclezs.com
16 |  * @since 2021/4/28 11:58
17 |  */
18 | @UtilityClass
19 | public class LanguageManager {
20 | 
21 |   private static final Map<String, String> LANG = new HashMap<>(16);
22 | 
23 |   static {
24 |     LANG.put("简体中文", "zh_CN");
25 |     LANG.put("繁体中文", "zh_TW");
26 |     LANG.put("English", "en");
27 |   }
28 | 
29 |   public static ObservableList<String> names() {
30 |     return FXCollections.observableArrayList(LANG.keySet());
31 |   }
32 | 
33 | 
34 |   /**
35 |    * 根据locale获取对应的名字
36 |    *
37 |    * @param locale Locale
38 |    * @return 如:简体中文
39 |    */
40 |   public static String name(Locale locale) {
41 |     String localeString = String.format("%s_%s", locale.getLanguage(), locale.getCountry());
42 |     for (Entry<String, String> entry : LANG.entrySet()) {
43 |       if (entry.getValue().equals(localeString)) {
44 |         return entry.getKey();
45 |       }
46 |     }
47 |     return null;
48 |   }
49 | 
50 |   /**
51 |    * 根据语言名字获取Locale
52 |    *
53 |    * @param name 名字如:English
54 |    * @return Locale
55 |    */
56 |   public static Locale locale(String name) {
57 |     String locale = LANG.get(name);
58 |     if (locale == null) {
59 |       return Locale.getDefault();
60 |     }
61 |     String[] localeSplit = locale.split("_");
62 |     if (localeSplit.length > 1) {
63 |       return new Locale(localeSplit[0], localeSplit[1]);
64 |     }
65 |     return new Locale(localeSplit[0]);
66 |   }
67 | }
68 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/model/BookBundle.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.model;
 2 | 
 3 | import com.unclezs.novel.analyzer.core.model.AnalyzerRule;
 4 | import com.unclezs.novel.analyzer.model.Novel;
 5 | import com.unclezs.novel.analyzer.util.SerializationUtils;
 6 | import com.unclezs.novel.app.main.views.home.DownloadManagerView;
 7 | import lombok.Data;
 8 | 
 9 | /**
10 |  * 小说下载数据包
11 |  *
12 |  * @author blog.unclezs.com
13 |  * @since 2021/4/30 15:18
14 |  * @see DownloadManagerView
15 |  */
16 | @Data
17 | public class BookBundle {
18 | 
19 |   /**
20 |    * 小说信息
21 |    */
22 |   private Novel novel;
23 |   /**
24 |    * 规则
25 |    */
26 |   private AnalyzerRule rule;
27 | 
28 |   /**
29 |    * 全部深克隆一份新的
30 |    *
31 |    * @param novel 小说信息
32 |    * @param rule  规则
33 |    */
34 |   public BookBundle(Novel novel, AnalyzerRule rule) {
35 |     this.novel = SerializationUtils.deepClone(novel);
36 |     this.rule = SerializationUtils.deepClone(rule);
37 |   }
38 | }
39 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/model/BookCache.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.model;
 2 | 
 3 | import com.unclezs.novel.analyzer.core.model.AnalyzerRule;
 4 | import com.unclezs.novel.analyzer.model.Chapter;
 5 | import lombok.AllArgsConstructor;
 6 | import lombok.Data;
 7 | import lombok.NoArgsConstructor;
 8 | 
 9 | import java.io.Serializable;
10 | import java.util.Collections;
11 | import java.util.List;
12 | 
13 | /**
14 |  * 书籍缓存
15 |  *
16 |  * @author blog.unclezs.com
17 |  * @since 2021/5/7 16:19
18 |  */
19 | @Data
20 | @AllArgsConstructor
21 | @NoArgsConstructor
22 | public class BookCache implements Serializable {
23 | 
24 |   /**
25 |    * 规则
26 |    */
27 |   private AnalyzerRule rule = new AnalyzerRule();
28 |   /**
29 |    * 目录
30 |    */
31 |   private List<Chapter> toc = Collections.emptyList();
32 | }
33 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/model/ChapterProperty.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.model;
 2 | 
 3 | import com.unclezs.novel.analyzer.model.Chapter;
 4 | import javafx.beans.property.BooleanProperty;
 5 | import javafx.beans.property.SimpleBooleanProperty;
 6 | 
 7 | /**
 8 |  * 章节属性
 9 |  *
10 |  * @author blog.unclezs.com
11 |  * @since 2021/4/25 16:14
12 |  */
13 | public class ChapterProperty {
14 | 
15 |   private final BooleanProperty selected;
16 |   private Chapter chapter;
17 | 
18 |   public ChapterProperty(Chapter chapter) {
19 |     this.chapter = chapter;
20 |     this.selected = new SimpleBooleanProperty(true);
21 |   }
22 | 
23 |   public boolean isSelected() {
24 |     return selected.get();
25 |   }
26 | 
27 |   public void setSelected(boolean selected) {
28 |     this.selected.set(selected);
29 |   }
30 | 
31 |   public BooleanProperty selectedProperty() {
32 |     return selected;
33 |   }
34 | 
35 |   public Chapter getChapter() {
36 |     return chapter;
37 |   }
38 | 
39 |   public void setChapter(Chapter chapter) {
40 |     this.chapter = chapter;
41 |   }
42 | 
43 |   @Override
44 |   public String toString() {
45 |     return chapter.getName();
46 |   }
47 | }
48 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/model/config/BackupConfig.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.model.config;
 2 | 
 3 | import com.unclezs.novel.analyzer.util.StringUtils;
 4 | import javafx.beans.property.ObjectProperty;
 5 | import javafx.beans.property.SimpleObjectProperty;
 6 | import lombok.Data;
 7 | 
 8 | /**
 9 |  * @author blog.unclezs.com
10 |  * @since 2021/5/10 23:10
11 |  */
12 | @Data
13 | public class BackupConfig {
14 | 
15 |   /**
16 |    * webdav配置
17 |    */
18 |   private ObjectProperty<String> url = new SimpleObjectProperty<>(StringUtils.EMPTY);
19 |   private ObjectProperty<String> username = new SimpleObjectProperty<>(StringUtils.EMPTY);
20 |   private ObjectProperty<String> password = new SimpleObjectProperty<>(StringUtils.EMPTY);
21 | 
22 |   /**
23 |    * 备份内容
24 |    */
25 |   private ObjectProperty<Boolean> bookshelf = new SimpleObjectProperty<>(true);
26 |   private ObjectProperty<Boolean> audio = new SimpleObjectProperty<>(true);
27 |   private ObjectProperty<Boolean> rule = new SimpleObjectProperty<>(true);
28 |   private ObjectProperty<Boolean> searchEngine = new SimpleObjectProperty<>(true);
29 |   private ObjectProperty<Boolean> setting = new SimpleObjectProperty<>(true);
30 | }
31 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/model/config/BasicConfig.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.model.config;
 2 | 
 3 | import com.unclezs.novel.app.main.manager.LanguageManager;
 4 | import com.unclezs.novel.app.main.views.home.HomeView;
 5 | import javafx.beans.property.ObjectProperty;
 6 | import javafx.beans.property.SimpleObjectProperty;
 7 | import javafx.scene.text.Font;
 8 | import lombok.Data;
 9 | 
10 | import java.util.Locale;
11 | 
12 | /**
13 |  * @author blog.unclezs.com
14 |  * @since 2021/6/1 0:32
15 |  */
16 | @Data
17 | public class BasicConfig {
18 | 
19 |   /**
20 |    * 是否退出时最小化到托盘
21 |    */
22 |   private ObjectProperty<Boolean> tray = new SimpleObjectProperty<>(false);
23 | 
24 |   /**
25 |    * 主题
26 |    */
27 |   private String theme = HomeView.DEFAULT_THEME;
28 |   /**
29 |    * 系统语言,读取默认
30 |    */
31 |   private ObjectProperty<String> lang = new SimpleObjectProperty<>(LanguageManager.name(Locale.getDefault()));
32 |   /**
33 |    * 系统默认字体
34 |    */
35 |   private ObjectProperty<String> fonts = new SimpleObjectProperty<>(Font.getDefault().getFamily());
36 | }
37 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/model/config/BookShelfConfig.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.model.config;
 2 | 
 3 | import com.unclezs.novel.app.main.views.home.FictionBookshelfView;
 4 | import javafx.beans.property.ObjectProperty;
 5 | import javafx.beans.property.SimpleObjectProperty;
 6 | import lombok.Data;
 7 | 
 8 | /**
 9 |  * @author blog.unclezs.com
10 |  * @since 2021/5/10 17:31
11 |  */
12 | @Data
13 | public class BookShelfConfig {
14 | 
15 |   /**
16 |    * 选中的分组
17 |    */
18 |   private String group = FictionBookshelfView.GROUP_ALL;
19 | 
20 |   /**
21 |    * 书架书籍自动更新
22 |    */
23 |   private ObjectProperty<Boolean> autoUpdate = new SimpleObjectProperty<>(false);
24 |   /**
25 |    * 总是显示标题
26 |    */
27 |   private ObjectProperty<Boolean> alwaysShowBookTitle = new SimpleObjectProperty<>(false);
28 | }
29 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/model/config/DownloadConfig.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.model.config;
 2 | 
 3 | import com.unclezs.novel.app.main.manager.ResourceManager;
 4 | import javafx.beans.property.ObjectProperty;
 5 | import javafx.beans.property.SimpleObjectProperty;
 6 | import lombok.Data;
 7 | 
 8 | /**
 9 |  * 下载配置
10 |  *
11 |  * @author blog.unclezs.com
12 |  * @since 2021/4/30 13:47
13 |  */
14 | @Data
15 | public class DownloadConfig {
16 | 
17 |   /**
18 |    * 下载目录
19 |    */
20 |   private ObjectProperty<String> folder = new SimpleObjectProperty<>(ResourceManager.DOWNLOAD_DIR.getAbsolutePath());
21 |   /**
22 |    * 下载线程数量
23 |    */
24 |   private ObjectProperty<Integer> threadNum = new SimpleObjectProperty<>(1);
25 |   /**
26 |    * 任务数量
27 |    */
28 |   private ObjectProperty<Integer> taskNum = new SimpleObjectProperty<>(1);
29 |   /**
30 |    * 重试次数
31 |    */
32 |   private ObjectProperty<Integer> retryNum = new SimpleObjectProperty<>(0);
33 |   /**
34 |    * true则 每章单独一个文件,不合并
35 |    */
36 |   private ObjectProperty<Boolean> volume = new SimpleObjectProperty<>(true);
37 |   /**
38 |    * 下载类型
39 |    */
40 |   private ObjectProperty<Boolean> mobi = new SimpleObjectProperty<>(false);
41 |   private ObjectProperty<Boolean> txt = new SimpleObjectProperty<>(true);
42 |   private ObjectProperty<Boolean> epub = new SimpleObjectProperty<>(false);
43 | }
44 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/model/config/HotKeyConfig.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.model.config;
 2 | 
 3 | import lombok.Data;
 4 | 
 5 | /**
 6 |  * @author blog.unclezs.com
 7 |  * @since 2021/6/2 19:32
 8 |  */
 9 | @Data
10 | public class HotKeyConfig {
11 | 
12 |   /**
13 |    * 阅读器下一页
14 |    */
15 |   private String readerNextPage = "RIGHT";
16 |   /**
17 |    * 阅读器上一页
18 |    */
19 |   private String readerPrePage = "LEFT";
20 |   /**
21 |    * 阅读器下一章
22 |    */
23 |   private String readerNextChapter = "DOWN";
24 |   /**
25 |    * 阅读器上一章
26 |    */
27 |   private String readerPreChapter = "UP";
28 |   /**
29 |    * 阅读器目录
30 |    */
31 |   private String readerToc = "ctrl C";
32 |   /**
33 |    * 老板键
34 |    */
35 |   private String globalBossKey = "alt U";
36 | 
37 |   /**
38 |    * 启用全局热键
39 |    */
40 |   private boolean enabledGlobal = true;
41 | }
42 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/model/config/TTSConfig.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.model.config;
 2 | 
 3 | import cn.hutool.core.net.RFC3986;
 4 | import cn.hutool.core.util.StrUtil;
 5 | import com.unclezs.novel.analyzer.request.RequestParams;
 6 | import lombok.Data;
 7 | 
 8 | import java.nio.charset.StandardCharsets;
 9 | 
10 | /**
11 |  * TTS配置
12 |  *
13 |  * @author blog.unclezs.com
14 |  * @since 2021/5/9 9:57
15 |  */
16 | @Data
17 | public class TTSConfig {
18 |   private RequestParams params;
19 |   private String name;
20 | 
21 |   public RequestParams getFormattedParams(String text) {
22 |     RequestParams requestParams = this.params.copy();
23 |     text = RFC3986.GEN_DELIMS.encode(RFC3986.GEN_DELIMS.encode(text, StandardCharsets.UTF_8), StandardCharsets.UTF_8);
24 |     if (StrUtil.isNotBlank(this.params.getBody())) {
25 |       requestParams.setBody(this.params.getBody().replace("{{text}}", text));
26 |     }
27 |     requestParams.setUrl(this.params.getUrl().replace("{{text}}", text));
28 |     return requestParams;
29 |   }
30 | }
31 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/test/DebounceTaskTest.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.test;
 2 | 
 3 | import com.unclezs.novel.app.framework.executor.DebounceTask;
 4 | 
 5 | /**
 6 |  * @author blog.unclezs.com
 7 |  * @since 2021/6/6 20:15
 8 |  */
 9 | public class DebounceTaskTest {
10 | 
11 |   public static void main(String[] args) {
12 |     DebounceTask task = DebounceTask.build(new Runnable() {
13 |       @Override
14 |       public void run() {
15 |         System.out.println("do task: " + System.currentTimeMillis());
16 |       }
17 |     }, 1000L, false);
18 |     long delay = 100;
19 |     //noinspection InfiniteLoopStatement
20 |     while (true) {
21 |       System.out.println("call task: " + System.currentTimeMillis());
22 |       task.run();
23 |       delay += 100;
24 |       try {
25 |         Thread.sleep(delay);
26 |       } catch (InterruptedException e) {
27 |         e.printStackTrace();
28 |       }
29 |     }
30 |   }
31 | }
32 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/test/DynamicPageTest.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.test;
 2 | 
 3 | import javafx.application.Application;
 4 | import javafx.concurrent.Worker.State;
 5 | import javafx.scene.Scene;
 6 | import javafx.scene.control.Button;
 7 | import javafx.scene.layout.VBox;
 8 | import javafx.scene.web.WebEngine;
 9 | import javafx.scene.web.WebView;
10 | import javafx.stage.Stage;
11 | 
12 | /**
13 |  * @author blog.unclezs.com
14 |  * @since 2021/5/24 20:48
15 |  */
16 | public class DynamicPageTest extends Application {
17 | 
18 |   @Override
19 |   public void start(Stage primaryStage) throws Exception {
20 |     WebView webView = new WebView();
21 |     WebEngine engine = webView.getEngine();
22 |     engine.getLoadWorker().stateProperty().addListener(e -> {
23 |       if (engine.getLoadWorker().getState() == State.SUCCEEDED) {
24 |         System.out.println(engine.executeScript("document.documentElement.outerHTML"));
25 |       }
26 |     });
27 |     engine.load("https://book.qidian.com/info/1735921");
28 |     Button button = new Button("打印源码");
29 |     button.setOnAction(e -> {
30 |       System.out.println(engine.executeScript("document.documentElement.outerHTML"));
31 |     });
32 |     VBox box = new VBox(button, webView);
33 |     Scene scene = new Scene(box, 800, 500);
34 |     primaryStage.setScene(scene);
35 |     primaryStage.show();
36 |   }
37 | }
38 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/test/TTSTest.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.test;
 2 | 
 3 | import com.unclezs.novel.app.framework.util.FontUtils;
 4 | 
 5 | import java.io.IOException;
 6 | 
 7 | /**
 8 |  * @author blog.unclezs.com
 9 |  * @since 2021/5/9 9:59
10 |  */
11 | public class TTSTest {
12 | 
13 |   public static void main(String[] args) throws IOException {
14 | 
15 |     System.out.println(FontUtils.getAllFontFamilies());
16 |   }
17 | }
18 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/util/TimeUtil.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.util;
 2 | 
 3 | import lombok.experimental.UtilityClass;
 4 | 
 5 | /**
 6 |  * 时间工具
 7 |  *
 8 |  * @author blog.unclezs.com
 9 |  * @since 2021/5/6 10:45
10 |  */
11 | @UtilityClass
12 | public class TimeUtil {
13 | 
14 |   /**
15 |    * 秒数转为时间格式(HH:mm:ss)<br> 参考:https://github.com/iceroot
16 |    *
17 |    * @param seconds 需要转换的秒数
18 |    * @return 转换后的字符串
19 |    */
20 |   public static String secondToTime(double seconds) {
21 |     if (seconds < 0) {
22 |       throw new IllegalArgumentException("秒数必须大于0");
23 |     }
24 | 
25 |     int hour = (int) (seconds / 3600);
26 |     int other = (int) (seconds % 3600);
27 |     int minute = other / 60;
28 |     int second = other % 60;
29 |     final StringBuilder sb = new StringBuilder();
30 |     if (hour != 0) {
31 |       if (hour < 10) {
32 |         sb.append("0");
33 |       }
34 |       sb.append(hour);
35 |       sb.append(":");
36 |     }
37 |     if (minute < 10) {
38 |       sb.append("0");
39 |     }
40 |     sb.append(minute);
41 |     sb.append(":");
42 |     if (second < 10) {
43 |       sb.append("0");
44 |     }
45 |     sb.append(second);
46 |     return sb.toString();
47 |   }
48 | }
49 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/views/animation/PrePageTransition.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.views.animation;
 2 | 
 3 | import javafx.animation.Interpolator;
 4 | import javafx.animation.KeyFrame;
 5 | import javafx.animation.KeyValue;
 6 | import javafx.animation.Timeline;
 7 | import javafx.animation.Transition;
 8 | import javafx.scene.effect.BlurType;
 9 | import javafx.scene.effect.DropShadow;
10 | import javafx.scene.layout.Region;
11 | import javafx.scene.paint.Color;
12 | import javafx.util.Duration;
13 | 
14 | /**
15 |  * @author blog.unclezs.com
16 |  * @since 2021/5/8 16:49
17 |  */
18 | public class PrePageTransition extends Transition {
19 | 
20 |   private static final DropShadow SHADOW =
21 |     new DropShadow(BlurType.THREE_PASS_BOX, Color.rgb(0, 0, 0, 0.26), 15, 0.26, 3, 0);
22 |   private final Timeline timeline;
23 |   private final Region node;
24 |   private final Region container;
25 | 
26 |   public PrePageTransition(Region node, Region container) {
27 |     this.container = container;
28 |     this.node = node;
29 |     this.timeline = new Timeline(
30 |       new KeyFrame(Duration.ZERO,
31 |         new KeyValue(node.translateXProperty(), -container.getWidth(), Interpolator.EASE_BOTH)
32 |       ),
33 |       new KeyFrame(Duration.millis(10),
34 |         new KeyValue(node.effectProperty(), SHADOW, Interpolator.EASE_BOTH)
35 |       ),
36 |       new KeyFrame(Duration.millis(600),
37 |         new KeyValue(node.translateXProperty(), 0, Interpolator.EASE_BOTH),
38 |         new KeyValue(node.effectProperty(), null, Interpolator.EASE_BOTH)
39 |       ));
40 |     setCycleDuration(Duration.millis(600));
41 |     setDelay(Duration.seconds(0));
42 |   }
43 | 
44 |   @Override
45 |   protected void interpolate(double d) {
46 |     timeline.getKeyFrames().set(0, new KeyFrame(Duration.ZERO,
47 |       new KeyValue(node.translateXProperty(), -container.getWidth(), Interpolator.EASE_BOTH)
48 |     ));
49 |     timeline.playFrom(Duration.seconds(d));
50 |     timeline.stop();
51 |   }
52 | }
53 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/views/components/cell/CheckBoxTableCell.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.views.components.cell;
 2 | 
 3 | import com.jfoenix.controls.JFXCheckBox;
 4 | import javafx.scene.control.CheckBox;
 5 | import javafx.scene.control.TableCell;
 6 | 
 7 | import java.util.function.ObjIntConsumer;
 8 | 
 9 | /**
10 |  * @author blog.unclezs.com
11 |  * @since 2021/4/20 22:53
12 |  */
13 | public class CheckBoxTableCell<S> extends TableCell<S, Boolean> {
14 | 
15 |   private final CheckBox checkBox = new JFXCheckBox();
16 | 
17 |   public CheckBoxTableCell(ObjIntConsumer<Boolean> onChange) {
18 |     checkBox.selectedProperty().addListener(e -> onChange.accept(checkBox.isSelected(), getTableRow().getIndex()));
19 |   }
20 | 
21 |   @Override
22 |   protected void updateItem(Boolean item, boolean empty) {
23 |     super.updateItem(item, empty);
24 |     if (item == null || empty) {
25 |       setText(null);
26 |       setGraphic(null);
27 |     } else {
28 |       checkBox.setSelected(item);
29 |       setGraphic(checkBox);
30 |     }
31 |   }
32 | 
33 | }
34 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/views/components/cell/DownloadHistoryActionTableCell.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.views.components.cell;
 2 | 
 3 | import cn.hutool.core.io.FileUtil;
 4 | import com.unclezs.novel.app.framework.components.icon.Icon;
 5 | import com.unclezs.novel.app.framework.components.icon.IconFont;
 6 | import com.unclezs.novel.app.framework.util.DesktopUtils;
 7 | import com.unclezs.novel.app.framework.util.NodeHelper;
 8 | import com.unclezs.novel.app.main.db.beans.DownloadHistory;
 9 | import javafx.scene.control.TableCell;
10 | import javafx.scene.control.Tooltip;
11 | import javafx.scene.layout.HBox;
12 | 
13 | import java.io.File;
14 | 
15 | /**
16 |  * @author blog.unclezs.com
17 |  * @since 2021/5/5 13:20
18 |  */
19 | public class DownloadHistoryActionTableCell extends TableCell<DownloadHistory, DownloadHistory> {
20 | 
21 |   private final HBox box;
22 |   private DownloadHistory item;
23 | 
24 |   public DownloadHistoryActionTableCell() {
25 |     Icon open = NodeHelper.addClass(new Icon(IconFont.FOLDER));
26 |     open.setTooltip(new Tooltip("打开"));
27 |     Icon clear = NodeHelper.addClass(new Icon(IconFont.CLEAR));
28 |     clear.setTooltip(new Tooltip("清除"));
29 |     this.box = NodeHelper.addClass(new HBox(open, clear), "action-cell");
30 |     open.setOnMouseClicked(e -> {
31 |       File dir = FileUtil.file(item.getPath());
32 |       if (FileUtil.exist(dir)) {
33 |         DesktopUtils.openDir(dir);
34 |       }
35 |     });
36 |     clear.setOnMouseClicked(e -> getTableView().getItems().remove(item));
37 |   }
38 | 
39 |   @Override
40 |   protected void updateItem(DownloadHistory item, boolean empty) {
41 |     super.updateItem(item, empty);
42 |     if (item == null || empty) {
43 |       setGraphic(null);
44 |       setText(null);
45 |     } else {
46 |       this.item = item;
47 |       setGraphic(box);
48 |     }
49 |   }
50 | }
51 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/views/components/cell/TagTableCell.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.views.components.cell;
 2 | 
 3 | import com.unclezs.novel.app.framework.components.Tag;
 4 | import javafx.scene.control.TableCell;
 5 | 
 6 | /**
 7 |  * 表格标签列
 8 |  *
 9 |  * @author blog.unclezs.com
10 |  * @since 2021/5/2 17:00
11 |  */
12 | public class TagTableCell<S> extends TableCell<S, String> {
13 | 
14 |   private final Tag tag;
15 | 
16 |   public TagTableCell() {
17 |     this.tag = new Tag();
18 |   }
19 | 
20 |   @Override
21 |   protected void updateItem(String item, boolean empty) {
22 |     super.updateItem(item, empty);
23 |     if (item == null || empty) {
24 |       setGraphic(null);
25 |       setText(null);
26 |     } else {
27 |       tag.setText(item);
28 |       setGraphic(tag);
29 |     }
30 |   }
31 | }
32 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/views/components/cell/TagsTableCell.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.views.components.cell;
 2 | 
 3 | import cn.hutool.core.util.StrUtil;
 4 | import com.unclezs.novel.app.framework.components.Tag;
 5 | import javafx.scene.control.TableCell;
 6 | import javafx.scene.layout.HBox;
 7 | 
 8 | import java.util.ArrayList;
 9 | import java.util.List;
10 | 
11 | /**
12 |  * 表格标签列
13 |  *
14 |  * @author blog.unclezs.com
15 |  * @since 2021/5/2 17:00
16 |  */
17 | public class TagsTableCell<S> extends TableCell<S, String> {
18 | 
19 |   private final List<Tag> tags = new ArrayList<>();
20 |   private final HBox box = new HBox();
21 | 
22 |   public TagsTableCell() {
23 |     box.setSpacing(3);
24 |   }
25 | 
26 |   @Override
27 |   protected void updateItem(String item, boolean empty) {
28 |     super.updateItem(item, empty);
29 |     if (item == null || empty) {
30 |       setGraphic(null);
31 |       setText(null);
32 |     } else {
33 |       String[] tagsText = item.split(StrUtil.COMMA);
34 |       box.getChildren().clear();
35 |       for (int i = 0; i < tagsText.length; i++) {
36 |         if (tags.size() < i + 1) {
37 |           tags.add(new Tag(tagsText[i]));
38 |         } else {
39 |           tags.get(i).setText(tagsText[i]);
40 |         }
41 |         box.getChildren().add(tags.get(i));
42 |       }
43 |       setGraphic(box);
44 |     }
45 |   }
46 | }
47 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/views/components/cell/TocListCell.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.views.components.cell;
 2 | 
 3 | import com.unclezs.novel.analyzer.model.Chapter;
 4 | import com.unclezs.novel.app.framework.components.cell.BaseListCell;
 5 | import com.unclezs.novel.app.framework.components.icon.Icon;
 6 | import com.unclezs.novel.app.framework.components.icon.IconFont;
 7 | import com.unclezs.novel.app.framework.util.NodeHelper;
 8 | import javafx.scene.control.Label;
 9 | import javafx.scene.layout.HBox;
10 | import javafx.scene.layout.Priority;
11 | 
12 | import java.util.function.Function;
13 | 
14 | /**
15 |  * @author blog.unclezs.com
16 |  * @since 2021/5/6 22:01
17 |  */
18 | public class TocListCell extends BaseListCell<Chapter> {
19 | 
20 |   public static final String DEFAULT_STYLE_CLASS = "toc-list-cell";
21 |   private final Icon icon = new Icon(IconFont.BOOKMARK);
22 |   private final Icon cached = new Icon(IconFont.ENABLED);
23 |   private final Label label = new Label();
24 |   private final HBox cell = new HBox();
25 |   private Function<Integer, Boolean> cacheStateGetter;
26 | 
27 |   public TocListCell() {
28 |     label.setMaxWidth(Double.MAX_VALUE);
29 |     HBox.setHgrow(label, Priority.ALWAYS);
30 |     this.cacheStateGetter = index -> getItem().getContent() != null;
31 |   }
32 | 
33 |   public TocListCell(Function<Integer, Boolean> cacheStateGetter) {
34 |     this();
35 |     this.cacheStateGetter = cacheStateGetter;
36 |   }
37 | 
38 |   @Override
39 |   protected void updateItem(Chapter item) {
40 |     NodeHelper.addClass(this, DEFAULT_STYLE_CLASS);
41 |     setGraphic(cell);
42 |     label.setGraphic(icon);
43 |     label.setText(item.getName());
44 |     cell.getChildren().setAll(label);
45 |     if (Boolean.TRUE.equals(cacheStateGetter.apply(getIndex()))) {
46 |       cell.getChildren().add(cached);
47 |     }
48 |   }
49 | }
50 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/views/components/rule/RuleItem.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.views.components.rule;
 2 | 
 3 | import com.unclezs.novel.app.framework.collection.SimpleObservableList;
 4 | import com.unclezs.novel.app.framework.util.NodeHelper;
 5 | import javafx.beans.DefaultProperty;
 6 | import javafx.scene.Node;
 7 | import javafx.scene.control.Label;
 8 | import javafx.scene.layout.HBox;
 9 | import javafx.scene.layout.Priority;
10 | import lombok.Getter;
11 | 
12 | import java.util.List;
13 | 
14 | /**
15 |  * @author blog.unclezs.com
16 |  * @since 2021/4/22 11:24
17 |  */
18 | @DefaultProperty("content")
19 | public class RuleItem extends HBox {
20 | 
21 |   @Getter
22 |   private final List<Node> content = new SimpleObservableList<>() {
23 |     @Override
24 |     public void onAdd(Node element) {
25 |       HBox.setHgrow(element, Priority.ALWAYS);
26 |       getChildren().add(element);
27 |     }
28 |   };
29 |   @Getter
30 |   private String name;
31 | 
32 | 
33 |   public RuleItem() {
34 |     getStyleClass().add("item");
35 |     Label nameLabel = NodeHelper.addClass(new Label(), "name");
36 |     getChildren().add(nameLabel);
37 |   }
38 | 
39 |   public void setName(String name) {
40 |     this.name = name;
41 |     Node label = getChildren().get(0);
42 |     if (label instanceof Label) {
43 |       ((Label) label).setText(name);
44 |     }
45 |   }
46 | 
47 | 
48 | }
49 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/views/components/setting/SettingItem.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.views.components.setting;
 2 | 
 3 | import com.unclezs.novel.app.framework.util.NodeHelper;
 4 | import javafx.beans.NamedArg;
 5 | import javafx.scene.Node;
 6 | import javafx.scene.control.Label;
 7 | import javafx.scene.layout.VBox;
 8 | import lombok.Getter;
 9 | 
10 | /**
11 |  * @author blog.unclezs.com
12 |  * @since 2021/4/22 11:24
13 |  */
14 | public class SettingItem extends VBox {
15 | 
16 |   @Getter
17 |   private Node content;
18 | 
19 |   public SettingItem(@NamedArg("name") String name, @NamedArg("content") Node content) {
20 |     getStyleClass().add("setting-item");
21 |     Label nameLabel = NodeHelper.addClass(new Label(name), "item-name");
22 |     VBox container = NodeHelper.addClass(new VBox(content), "item-content");
23 |     getChildren().addAll(nameLabel, container);
24 |   }
25 | }
26 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/views/components/setting/SettingItems.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.views.components.setting;
 2 | 
 3 | import com.unclezs.novel.app.framework.util.NodeHelper;
 4 | import javafx.beans.NamedArg;
 5 | import javafx.scene.Node;
 6 | import javafx.scene.control.Label;
 7 | import javafx.scene.layout.VBox;
 8 | import lombok.Getter;
 9 | 
10 | import java.util.List;
11 | 
12 | /**
13 |  * @author blog.unclezs.com
14 |  * @since 2021/4/28 11:29
15 |  */
16 | public class SettingItems extends VBox {
17 | 
18 |   private final Label nameLabel;
19 |   private final VBox itemBox;
20 |   @Getter
21 |   private List<Node> items;
22 | 
23 |   public SettingItems(@NamedArg("name") String name, @NamedArg("items") List<Node> items) {
24 |     this(name);
25 |     setItems(items.toArray(Node[]::new));
26 |   }
27 | 
28 |   public SettingItems(String name) {
29 |     getStyleClass().addAll("setting-items");
30 |     nameLabel = NodeHelper.addClass(new Label(name), "items-name");
31 |     itemBox = NodeHelper.addClass(new VBox(), "items-box");
32 |     getChildren().addAll(nameLabel, itemBox);
33 |   }
34 | 
35 |   public void setItems(Node... items) {
36 |     itemBox.getChildren().setAll(items);
37 |   }
38 | }
39 | 


--------------------------------------------------------------------------------
/app/src/main/java/com/unclezs/novel/app/main/views/reader/PageView.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.main.views.reader;
 2 | 
 3 | import com.unclezs.novel.app.framework.util.NodeHelper;
 4 | import com.unclezs.novel.app.main.views.animation.NextPageTransition;
 5 | import com.unclezs.novel.app.main.views.animation.PrePageTransition;
 6 | import javafx.animation.Transition;
 7 | import javafx.beans.NamedArg;
 8 | import javafx.scene.control.ContentDisplay;
 9 | import javafx.scene.control.Label;
10 | import javafx.scene.control.OverrunStyle;
11 | import javafx.scene.layout.Region;
12 | import lombok.Getter;
13 | 
14 | /**
15 |  * @author blog.unclezs.com
16 |  * @since 2021/5/8 19:06
17 |  */
18 | public class PageView extends Label {
19 | 
20 |   public static final String DEFAULT_STYLE_CLASS = "page-view";
21 |   @Getter
22 |   private final Transition preTransition;
23 |   @Getter
24 |   private final Transition nextTransition;
25 |   @Getter
26 |   private final Label title = NodeHelper.addClass(new Label(), "title");
27 | 
28 | 
29 |   public PageView(@NamedArg("container") Region container) {
30 |     preTransition = new PrePageTransition(this, container);
31 |     nextTransition = new NextPageTransition(this, container);
32 |     this.setWrapText(true);
33 |     this.setTextOverrun(OverrunStyle.CLIP);
34 |     this.setMaxHeight(Double.MAX_VALUE);
35 |     this.getStyleClass().add(DEFAULT_STYLE_CLASS);
36 |     this.setContentDisplay(ContentDisplay.TOP);
37 |     this.title.maxWidthProperty().bind(widthProperty());
38 |   }
39 | 
40 |   /**
41 |    * 设置标题
42 |    *
43 |    * @param titleText 标题
44 |    */
45 |   public void setTitle(String titleText) {
46 |     if (titleText == null) {
47 |       this.setGraphic(null);
48 |     } else {
49 |       this.setGraphic(this.title);
50 |       this.title.setText(titleText);
51 |     }
52 |   }
53 | 
54 | }
55 | 


--------------------------------------------------------------------------------
/app/src/main/resources/assets/defaults/tts.json:
--------------------------------------------------------------------------------
 1 | [
 2 |   {
 3 |     "params": {
 4 |       "url": "http://tts.baidu.com/text2audio",
 5 |       "method": "POST",
 6 |       "body": "tex={{text}}&per=106&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=301&vol=5&aue=3&pit=5&_res_tag_=audio"
 7 |     },
 8 |     "name": "度博文"
 9 |   },
10 |   {
11 |     "params": {
12 |       "url": "https://dds.dui.ai/runtime/v1/synthesize?voiceId=gdfanfp&text={{text}}&&speed=10&volume=100&audioType=mp3",
13 |       "method": "GET"
14 |     },
15 |     "name": "思必驰 精品女声 客服芳芳"
16 |   },
17 |   {
18 |     "params": {
19 |       "url": "https://dds.dui.ai/runtime/v1/synthesize?voiceId=zhilingfp&text={{text}}&&speed=10&volume=100&audioType=mp3",
20 |       "method": "GET"
21 |     },
22 |     "name": "思必驰 精品女声 小玲甜美女神"
23 |   },
24 |   {
25 |     "params": {
26 |       "url": "https://dds.dui.ai/runtime/v1/synthesize?voiceId=zxcmp&text={{text}}&&speed=10&volume=100&audioType=mp3",
27 |       "method": "GET"
28 |     },
29 |     "name": "思必驰 精品男声 风趣幽默星爷"
30 |   },
31 |   {
32 |     "params": {
33 |       "url": "https://dds.dui.ai/runtime/v1/synthesize?voiceId=gqlanfp&text={{text}}&&speed=10&volume=100&audioType=mp3",
34 |       "method": "GET"
35 |     },
36 |     "name": "思必驰 精品女声 温柔小兰"
37 |   },
38 |   {
39 |     "params": {
40 |       "url": "https://dds.dui.ai/runtime/v1/synthesize?voiceId=geyoump&text={{text}}&&speed=10&volume=100&audioType=mp3",
41 |       "method": "GET"
42 |     },
43 |     "name": "思必驰 精品男声 风趣淡定葛爷"
44 |   },
45 |   {
46 |     "params": {
47 |       "url": "https://dds.dui.ai/runtime/v1/synthesize?voiceId=ppangf_csn&text={{text}}&&speed=10&volume=100&audioType=mp3",
48 |       "method": "GET"
49 |     },
50 |     "name": "思必驰 精品女声 四川话胖胖"
51 |   },
52 |   {
53 |     "params": {
54 |       "url": "https://dds.dui.ai/runtime/v1/synthesize?voiceId=gdgmfp&text={{text}}&&speed=10&volume=100&audioType=mp3",
55 |       "method": "GET"
56 |     },
57 |     "name": "思必驰 精品男声 郭德纲"
58 |   }
59 | ]


--------------------------------------------------------------------------------
/app/src/main/resources/assets/images/no-cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/src/main/resources/assets/images/no-cover.jpg


--------------------------------------------------------------------------------
/app/src/main/resources/assets/images/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/src/main/resources/assets/images/plus.png


--------------------------------------------------------------------------------
/app/src/main/resources/assets/images/reward.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/src/main/resources/assets/images/reward.jpg


--------------------------------------------------------------------------------
/app/src/main/resources/assets/logo/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/src/main/resources/assets/logo/icon-128.png


--------------------------------------------------------------------------------
/app/src/main/resources/assets/logo/icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/src/main/resources/assets/logo/icon-16.png


--------------------------------------------------------------------------------
/app/src/main/resources/assets/logo/icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/src/main/resources/assets/logo/icon-32.png


--------------------------------------------------------------------------------
/app/src/main/resources/assets/logo/icon-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/src/main/resources/assets/logo/icon-48.png


--------------------------------------------------------------------------------
/app/src/main/resources/assets/logo/icon-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/src/main/resources/assets/logo/icon-64.png


--------------------------------------------------------------------------------
/app/src/main/resources/css/define/_global.scss:
--------------------------------------------------------------------------------
 1 | @import "_variables";
 2 | @import "_colors";
 3 | 
 4 | @mixin shadow {
 5 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 20, 0.19, 0, 6);
 6 | }
 7 | 
 8 | @mixin shadow-1() {
 9 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
10 | }
11 | 
12 | @mixin shadow-2() {
13 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 15, 0.16, 0, 4);
14 | }
15 | 
16 | @mixin shadow-3() {
17 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 20, 0.16, 0, 6);
18 | }
19 | 
20 | @mixin shadow-4() {
21 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 25, 0.25, 0, 8);
22 | }
23 | 
24 | @mixin shadow-5() {
25 |   -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 30, 0.30, 0, 10);
26 | }
27 | 
28 | // shadow-bottom-right radius=3
29 | @mixin shadow-br-3($color) {
30 |   -fx-effect: dropshadow(three-pass-box, $color, 3, 0, 1, 1) !important;
31 | }
32 | 
33 | @mixin shadow-br-1($color) {
34 |   -fx-effect: dropshadow(three-pass-box, $color, 1, 0, 1, 1) !important;
35 | }
36 | 
37 | // 稍微暗点的 字体自适应背景
38 | @mixin ladderTextFill($bgColor) {
39 |   -fx-text-fill: ladder($bgColor, $white_color 59%, $black_color 60%);
40 | }
41 | 
42 | // 纯色黑白 字体自适应背景
43 | @mixin ladderTextFillLight($bgColor) {
44 |   -fx-text-fill: ladder($bgColor, white 59%, black 60%);
45 | }
46 | 
47 | @mixin ladderTextFillForce($bgColor) {
48 |   -fx-text-fill: ladder($bgColor, $white_color 59%, $black_color 60%) !important;
49 | }
50 | 
51 | // 背景色
52 | @mixin ladderBgColor($bgColor) {
53 |   -fx-background-color: ladder($bgColor, $white_color 59%, $black_color 60%);
54 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/define/_variables.scss:
--------------------------------------------------------------------------------
1 | $stage-border-radius: 5
2 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/blue.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(52, 152, 219);
5 | $header-bg-color: $theme-color;
6 | $theme-color-light: rgba(52, 152, 219, 0.2);
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/dark.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/_global.scss";
 2 | @import "default";
 3 | 
 4 | $base: rgb(43, 43, 43);
 5 | $theme-bg-color: $base;
 6 | 
 7 | $theme-color: rgb(51, 51, 51);
 8 | $theme-color-dark: $md_grey-600;
 9 | $theme-color-light: rgba(224, 224, 224, 0.2);
10 | $theme-hover-color: rgb(51, 51, 51);
11 | $theme-border-color: rgb(67, 67, 67);
12 | $theme-icon-hover-color: #FFF;
13 | $sidebar-bg-color: $theme-bg-color;
14 | $header-bg-color: rgb(35, 35, 38);
15 | 
16 | @import "theme";
17 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/deep-blue.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(79, 98, 208);
5 | $theme-color-light: rgba(79, 98, 208, 0.2);
6 | $header-bg-color: $theme-color;
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/default.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/_global.scss";
 2 | 
 3 | $base: #FFF;
 4 | $theme-bg-color: $base;
 5 | 
 6 | $theme-color: #3b4252;
 7 | $theme-color-dark: $md_grey-600;
 8 | $theme-color-light: rgba(59, 66, 82, 0.2);
 9 | $theme-hover-color: $md_grey_400;
10 | $theme-icon-hover-color: $theme-color;
11 | $theme-border-color: rgb(224, 224, 224);
12 | $sidebar-bg-color: $theme-bg-color;
13 | $header-bg-color: $theme-color;
14 | 
15 | @import "theme";


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/green.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/_global.scss";
 2 | @import "default";
 3 | 
 4 | // 按钮选中颜色
 5 | $theme-color: rgb(77, 175, 124);
 6 | $theme-color-light: rgba(77, 175, 124, 0.2);
 7 | $header-bg-color: $theme-color;
 8 | $theme-icon-hover-color: $theme-color;
 9 | @import "theme";
10 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/grey.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(108, 122, 137);
5 | $header-bg-color: $theme-color;
6 | $theme-color-light: rgba(108, 122, 137, 0.2);
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/image.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/_global.scss";
 2 | @import "default";
 3 | 
 4 | $base: rgb(43, 43, 43);
 5 | // 主题背景色
 6 | $theme-bg-color: transparent;
 7 | // 相对背景的暗色(分隔符、提示文字)
 8 | $theme-color-dark: $md_grey_600;
 9 | // 按钮选中颜色
10 | $theme-color: $md_teal_600;
11 | // 按钮hover颜色
12 | $theme-hover-color: $md_grey_100;
13 | 
14 | $sidebar-bg-color: transparent;
15 | $header-bg-color: transparent;
16 | 
17 | @import "theme";
18 | 
19 | .stage-decorator-container {
20 |   -fx-background-image: url('/assets/images/bg.jpg');
21 | }
22 | 
23 | .jfx-popup-container {
24 |   -fx-background-image: url('/assets/images/bg.jpg');
25 | }
26 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/light-blue.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(57, 175, 234);
5 | $theme-color-light: rgba(57, 175, 234, 0.2);
6 | $header-bg-color: $theme-color;
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/orange.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(245, 171, 53);
5 | $header-bg-color: $theme-color;
6 | $theme-color-light: rgba(245, 171, 53, 0.2);
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/pink.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(214, 130, 141);
5 | $header-bg-color: $theme-color;
6 | $theme-color-light: rgba(214, 130, 141, 0.2);
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/purple.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(155, 89, 182);
5 | $theme-color-light: rgba(155, 89, 182, 0.2);
6 | $header-bg-color: $theme-color;
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/red.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(214, 69, 65);
5 | $header-bg-color: $theme-color;
6 | $theme-color-light: rgba(214, 69, 65, 0.2);
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/teal.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(51, 110, 123);
5 | $theme-color-light: rgba(51, 110, 123, 0.2);
6 | $header-bg-color: $theme-color;
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/theme/yellow.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(233, 212, 96);
5 | $theme-color-light: rgba(233, 212, 96, 0.2);
6 | $header-bg-color: $theme-color;
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_analysis-download.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/global";
2 | 
3 | .analysis-download {
4 | 
5 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_download-manager.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/global";
 2 | 
 3 | $tab-radius: 5px;
 4 | 
 5 | .download-manager {
 6 |   .tab-button {
 7 |     &.history-tab {
 8 |       -fx-border-radius: 0 $tab-radius $tab-radius 0;
 9 |       -fx-background-radius: 0 $tab-radius $tab-radius 0;
10 |     }
11 | 
12 |     &.tasks-tab {
13 |       -fx-border-radius: $tab-radius 0 0 $tab-radius;
14 |       -fx-background-radius: $tab-radius 0 0 $tab-radius;
15 |     }
16 |   }
17 | 
18 |   .table-view {
19 |     .table-row-cell {
20 |       &:selected, &:filled:selected, &:filled > .table-cell:selected {
21 |         -fx-table-cell-border-color: transparent;
22 |         -fx-background-color: -fx-base;
23 | 
24 |         .table-cell, .icon, .label {
25 |           @include ladderTextFill(-fx-base);
26 |         }
27 | 
28 |         .check-box .box {
29 |           -fx-border-color: ladder(-fx-base, $white_color 49%, $black_color 50%);
30 |         }
31 |       }
32 |     }
33 | 
34 |     .action-cell {
35 |       .icon {
36 |         &:hover {
37 |           -fx-text-fill: theme-icon-hover-color !important;
38 |         }
39 |       }
40 |     }
41 | 
42 |     .progress-cell {
43 |       -fx-alignment: center-left;
44 | 
45 |       .progress-info-box {
46 |         .label {
47 |           -fx-font-size: 11px;
48 |         }
49 |       }
50 |     }
51 | 
52 |     .download-action-col {
53 |       .label {
54 |         -fx-padding: 0 0 0 30px;
55 |       }
56 |     }
57 | 
58 |     .download-action-cell {
59 |       -fx-alignment: center-left !important;
60 | 
61 |       .download-action {
62 |         -fx-padding: 0 0 0 10px;
63 |         -fx-alignment: center-left !important;
64 |       }
65 |     }
66 |   }
67 | }
68 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_fiction-bookshelf.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/global";
 2 | 
 3 | .fiction-bookshelf {
 4 |   .group-panel {
 5 |     -fx-background-color: -fx-base;
 6 |     -jfx-disable-animation: true;
 7 |   }
 8 | 
 9 |   .book-node {
10 |     $node-width: 95px;
11 |     $node-height: 120px;
12 |     -fx-padding: 0 !important;
13 | 
14 |     .book-node-container {
15 |       -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 15, 0.16, 0, 4);
16 |       -fx-background-color: -fx-base;
17 |       -fx-pref-height: $node-height !important;
18 |       -fx-pref-width: $node-width !important;
19 |       -fx-min-width: -fx-pref-width;
20 |       -fx-max-width: -fx-pref-width;
21 |       -fx-max-height: -fx-pref-height;
22 |       -fx-min-height: -fx-pref-height;
23 |       -fx-padding: 0;
24 |       -fx-cursor: hand;
25 | 
26 |       .title {
27 |         -fx-wrap-text: true;
28 |         -fx-alignment: center;
29 |         -fx-pref-width: $node-width;
30 |         -fx-background-color: rgba(0, 0, 0, 0.46);
31 |         -fx-text-fill: white;
32 |         -fx-padding: 2px;
33 |         -fx-opacity: 0;
34 | 
35 |         &.show-title {
36 |           -fx-opacity: 1;
37 |         }
38 |       }
39 | 
40 | 
41 |       &:hover {
42 |         .title {
43 |           -fx-opacity: 1;
44 |         }
45 |       }
46 | 
47 |       .tip {
48 |         -fx-text-fill: white;
49 |         -fx-background-color: red;
50 |         -fx-padding: 1px 5px 1px 5px;
51 |         -fx-border-radius: 10 0 0 10;
52 |         -fx-background-radius: 10 0 0 10;
53 |       }
54 |     }
55 |   }
56 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_header-menu.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/global";
 2 | 
 3 | .home-setting-popup {
 4 |   @include ladderTextFill(theme-bg-color);
 5 |   -fx-padding: 10px;
 6 |   -fx-alignment: center-left;
 7 |   -fx-background-color: theme-bg-color;
 8 | 
 9 |   .icon-button {
10 |     -fx-background-color: transparent;
11 |     -fx-border-color: transparent;
12 |     -fx-font-size: 13px;
13 |     -fx-pref-width: 140px;
14 |     -fx-text-fill: inherit;
15 |     -fx-border-radius: 3px;
16 |     -fx-min-height: 30px;
17 |     -fx-background-radius: 3px;
18 |     -fx-alignment: center-left;
19 |     -fx-padding: 0 0 0 10px;
20 | 
21 |     &:hover {
22 |       @include ladderTextFill(theme-color);
23 |       @include shadow-br-3(derive(-fx-text-fill, -100%));
24 |       -fx-background-color: theme-color;
25 |     }
26 | 
27 |     .icon {
28 |       -fx-icon-size: 14px;
29 |       -fx-max-width: 18px;
30 |       -fx-min-width: 18px;
31 |       -fx-pref-width: 18px;
32 |       -fx-text-fill: inherit;
33 |     }
34 |   }
35 | 
36 |   .home-setting-popup-delimiter {
37 |     -fx-max-height: 1px;
38 |     -fx-min-height: 1px;
39 |     -fx-background-color: theme-color-dark;
40 |   }
41 | }
42 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_import-book.scss:
--------------------------------------------------------------------------------
1 | .import-book {
2 |   .combo-box {
3 | 
4 |     & > .list-cell {
5 |       -fx-alignment: center-left;
6 |       -fx-padding: 1px 0 1px 8px;
7 |     }
8 |   }
9 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_rule-editor.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/global";
 2 | 
 3 | .rule-editor {
 4 |   .container {
 5 |     .rule-container {
 6 |       -fx-padding: 10px 10px 10px 0;
 7 | 
 8 |       .items {
 9 |         -fx-padding: 0 0 10px 5px;
10 |         -fx-spacing: 10px;
11 | 
12 |         &.no-name {
13 |           -fx-padding: 0 0 10px 0;
14 | 
15 |           & > .item > .name {
16 |             -fx-min-width: 70px;
17 |             -fx-max-width: 70px;
18 |             -fx-padding: 0;
19 |           }
20 |         }
21 | 
22 |         & > .title-box {
23 |           -fx-alignment: center-left;
24 | 
25 |           & > .title {
26 |             -fx-font-size: 14px;
27 |             -fx-border-width: 0 0 0 4px;
28 |             -fx-border-color: theme-color;
29 |             -fx-padding: 0 0 0 5px;
30 |           }
31 |         }
32 | 
33 |         .item {
34 |           -fx-spacing: 10px;
35 |           -fx-alignment: center-left;
36 | 
37 |           & > .name {
38 |             -fx-min-width: 90px;
39 |             -fx-max-width: 90px;
40 |             -fx-padding: 0 0 0 15px;
41 |           }
42 |         }
43 |       }
44 |     }
45 |   }
46 | }
47 | 
48 | .common-rule-editor {
49 |   .action-box {
50 |     -fx-alignment: center-left;
51 |     -fx-spacing: 10;
52 | 
53 |     .combo-box {
54 |       -fx-pref-width: 120;
55 |       -fx-alignment: center-left;
56 |     }
57 |   }
58 | 
59 |   .title-label {
60 |     -fx-font-size: 13px !important;
61 |   }
62 | 
63 |   .button {
64 |     @include shadow-1();
65 |     -fx-pref-height: 30px;
66 |   }
67 | }
68 | 
69 | .params-editor {
70 |   -fx-spacing: 10;
71 | 
72 |   .item {
73 |     -fx-spacing: 10px;
74 |     -fx-alignment: center-left;
75 | 
76 |     .label, .text-field, .text-area {
77 |       -fx-font-size: 12px !important;
78 |     }
79 | 
80 |     .check-box {
81 |       -jfx-disable-visual-focus: true;
82 |     }
83 | 
84 |     .text-area {
85 |       -fx-max-height: 70px;
86 |     }
87 | 
88 |     & > .name {
89 |       -fx-min-width: 60px;
90 |       -fx-max-width: 60px;
91 |       -fx-padding: 0;
92 |     }
93 |   }
94 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_rule-manager.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/global";
 2 | 
 3 | .rule-manager {
 4 |   .container {
 5 |     .table-view {
 6 |       -fx-padding: 0;
 7 |     }
 8 |   }
 9 | }
10 | 
11 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_search-audio.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/global";
 2 | 
 3 | .search-audio {
 4 |   .container {
 5 |     -fx-spacing: 0;
 6 | 
 7 |     .result-list {
 8 | 
 9 |       .list-cell {
10 |         -fx-content-display: GRAPHIC_ONLY;
11 |         -fx-padding: 5px 5px 5px 8px;
12 | 
13 |         .cell {
14 |           -fx-alignment: center-left;
15 |           -fx-spacing: 10;
16 | 
17 |           .title {
18 |             -fx-font-size: 14px;
19 |             -fx-font-weight: bold;
20 |           }
21 | 
22 |           .tags {
23 |             -fx-spacing: 10px;
24 |           }
25 |         }
26 |       }
27 |     }
28 | 
29 |     .progress-bar {
30 |       -fx-min-height: 2;
31 | 
32 |       .bar {
33 |         -fx-padding: 2;
34 |       }
35 |     }
36 |   }
37 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_search-network.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/global";
 2 | 
 3 | .search-network {
 4 |   .progress-bar {
 5 |     -fx-min-height: 2;
 6 | 
 7 |     .bar {
 8 |       -fx-padding: 2;
 9 |     }
10 |   }
11 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_search-novel.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/global";
 2 | 
 3 | .search-novel {
 4 |   .container {
 5 |     -fx-spacing: 0;
 6 | 
 7 |     .result-list {
 8 | 
 9 |       .list-cell {
10 |         -fx-content-display: GRAPHIC_ONLY;
11 |         -fx-padding: 5px 5px 5px 8px;
12 | 
13 |         .cell {
14 |           -fx-alignment: center-left;
15 |           -fx-spacing: 10;
16 | 
17 |           .title {
18 |             -fx-font-size: 14px;
19 |             -fx-font-weight: bold;
20 |           }
21 | 
22 |           .tags {
23 |             -fx-spacing: 10px;
24 |           }
25 |         }
26 |       }
27 |     }
28 | 
29 |     .progress-bar {
30 |       -fx-min-height: 2;
31 | 
32 |       .bar {
33 |         -fx-padding: 2;
34 |       }
35 |     }
36 |   }
37 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/_setting.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/global";
 2 | @import "widgets/search-engine-editor";
 3 | 
 4 | .setting-view {
 5 |   -fx-padding: 10px;
 6 |   -fx-border-color: transparent;
 7 |   -fx-background-color: transparent;
 8 | 
 9 |   .setting-items {
10 |     -fx-spacing: 10px;
11 | 
12 |     .items-name {
13 |       -fx-border-color: theme-color;
14 |       -fx-border-width: 0 0 0 5px;
15 |       -fx-padding: 0 0 0 5px;
16 |       -fx-font-size: 14px;
17 |     }
18 | 
19 |     .items-box {
20 |       -fx-padding: 0 0 0 15px;
21 |       -fx-spacing: 20px;
22 | 
23 |       .setting-item {
24 |         -fx-alignment: center-left;
25 |         -fx-spacing: 10px;
26 | 
27 |         .item-name {
28 |           -fx-text-fill: theme-color-dark;
29 |         }
30 | 
31 |         .item-content {
32 |           -fx-spacing: 10px;
33 |           -fx-padding: 0 0 0 20px;
34 |         }
35 |       }
36 |     }
37 |   }
38 | 
39 |   .search-engine-setting {
40 |     .search-engine-manager {
41 |       -fx-max-height: 150px;
42 |       -fx-alignment: top-right;
43 | 
44 |       .actions {
45 |         -fx-alignment: center-right;
46 |         -fx-spacing: 10;
47 |       }
48 |     }
49 | 
50 |     .items-box {
51 |       -fx-padding: 0;
52 |     }
53 |   }
54 | }
55 | 
56 | 
57 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/widgets/_book-detail.scss:
--------------------------------------------------------------------------------
 1 | @import "../../../define/global";
 2 | 
 3 | .book-detail {
 4 |   -fx-spacing: 10;
 5 |   -fx-alignment: center-left;
 6 | 
 7 |   .info {
 8 |     -fx-spacing: 5px;
 9 |     -fx-min-width: 280px;
10 |     -fx-max-width: 280px;
11 | 
12 |     .label {
13 |       -fx-font-size: 12px !important;
14 |     }
15 | 
16 |     .item {
17 |       -fx-alignment: center-left;
18 | 
19 |       &.title {
20 |         .item-content {
21 |           -fx-font-size: 15px !important;
22 |           -fx-font-weight: bold !important;
23 |         }
24 |       }
25 | 
26 |       .editable-box {
27 |         -fx-alignment: center-left;
28 |         -fx-spacing: 5px;
29 | 
30 |         .text-field {
31 |           -fx-font-size: 12px !important;
32 |           -fx-font-weight: normal !important;
33 |         }
34 | 
35 |         .icon {
36 |           -fx-min-width: 20px;
37 |         }
38 | 
39 |         .label {
40 |           -fx-max-width: 200px;
41 |         }
42 |       }
43 | 
44 |       .item-label {
45 |         -fx-min-width: 30px;
46 |       }
47 |     }
48 |   }
49 | 
50 |   .desc .label {
51 |     -fx-font-size: 12px !important;
52 |     -fx-max-height: 200px;
53 |   }
54 | 
55 |   .actions {
56 |     -fx-spacing: 20px;
57 |   }
58 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/views/widgets/_search-engine-editor.scss:
--------------------------------------------------------------------------------
1 | @import "../../../define/global";
2 | 
3 | .search-engine-editor {
4 |   -fx-spacing: 10;
5 | 
6 |   .label {
7 |     -fx-font-size: 12px !important;
8 |   }
9 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/webview/baidu.css:
--------------------------------------------------------------------------------
 1 | #head,
 2 | #foot,
 3 | #s_tab,
 4 | #rs,
 5 | .result-op,
 6 | .result.c-container:not(.new-pmd),
 7 | #content_right {
 8 |   display: none;
 9 |   height: 0 !important;
10 |   width: 0 !important;
11 |   max-width: 0 !important;
12 |   min-width: 0 !important;
13 | }
14 | 
15 | #wrapper,
16 | #container,
17 | body {
18 |   width: 500px !important;
19 |   max-width: 500px !important;
20 |   min-width: 500px !important;
21 |   margin: 0 0 0 5px !important;
22 | }
23 | 
24 | .page-inner {
25 |   background-color: #FFF;
26 |   padding: 0 0 10px 0 !important;
27 | }
28 | 
29 | .c-container {
30 |   box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
31 |   transition: 0.3s;
32 |   border-radius: 5px;
33 |   padding: 10px 16px;
34 |   margin: 10px 0 10px 5px !important;
35 | }
36 | 
37 | .c-container .c-container {
38 |   box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.2);
39 |   border-radius: 0;
40 |   padding: 0 16px 0 0;
41 |   margin: 10px 0 10px 5px !important;
42 | }
43 | 
44 | .c-container:hover {
45 |   box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
46 | }
47 | 
48 | .c-container .c-container:hover {
49 |   box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.2);
50 | }
51 | 
52 | a > strong {
53 |   font-size: 16px;
54 |   color: red !important;
55 | }
56 | 
57 | a:not(strong) {
58 |   font-size: 16px;
59 |   color: black !important;
60 | }
61 | 
62 | a,
63 | em {
64 |   text-decoration: none !important;
65 | }
66 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/webview/baidu.scss:
--------------------------------------------------------------------------------
 1 | #head,
 2 | #foot,
 3 | #s_tab,
 4 | #rs,
 5 | .result-op,
 6 | .result.c-container:not(.new-pmd),
 7 | #content_right {
 8 |   display: none;
 9 |   height: 0 !important;
10 |   width: 0 !important;
11 |   max-width: 0 !important;
12 |   min-width: 0 !important;
13 | }
14 | 
15 | #wrapper,
16 | #container,
17 | body {
18 |   width: 500px !important;
19 |   max-width: 500px !important;
20 |   min-width: 500px !important;
21 |   margin: 0 0 0 5px !important;
22 | }
23 | 
24 | .page-inner {
25 |   background-color: #FFF;
26 |   padding: 0 0 10px 0 !important;
27 | }
28 | 
29 | .c-container {
30 |   box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
31 |   transition: 0.3s;
32 |   border-radius: 5px;
33 |   padding: 10px 16px;
34 |   margin: 10px 0 10px 5px !important;
35 | }
36 | 
37 | .c-container .c-container {
38 |   box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.2);
39 |   border-radius: 0;
40 |   padding: 0 16px 0 0;
41 |   margin: 10px 0 10px 5px !important;
42 | }
43 | 
44 | 
45 | .c-container:hover {
46 |   box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
47 | }
48 | 
49 | .c-container .c-container:hover {
50 |   box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.2);
51 | }
52 | 
53 | a > strong {
54 |   font-size: 16px;
55 |   color: red !important;
56 | }
57 | 
58 | a:not(strong) {
59 |   font-size: 16px;
60 |   color: black !important;
61 | }
62 | 
63 | a,
64 | em {
65 |   text-decoration: none !important;
66 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/webview/bing.css:
--------------------------------------------------------------------------------
 1 | #b_header,
 2 | #b_context,
 3 | #mfa_root,
 4 | #ev_talkbox_wrapper,
 5 | #b_footer,
 6 | .b_ans,
 7 | .b_rs {
 8 |   display: none;
 9 |   height: 0 !important;
10 |   width: 0 !important;
11 |   max-width: 0 !important;
12 |   min-width: 0 !important;
13 | }
14 | 
15 | body,
16 | #b_content {
17 |   width: 500px !important;
18 |   max-width: 500px !important;
19 |   min-width: 500px !important;
20 |   margin: 0 0 0 0px !important;
21 |   padding: 0 !important;
22 | }
23 | 
24 | .b_algo {
25 |   box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
26 |   transition: 0.3s;
27 |   border-radius: 5px;
28 |   padding: 10px 16px;
29 |   margin: 10px 0 10px 5px !important;
30 | }
31 | 
32 | .b_algo:hover {
33 |   box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
34 | }
35 | 
36 | a > strong {
37 |   font-size: 16px;
38 |   color: red !important;
39 | }
40 | 
41 | a:not(strong) {
42 |   font-size: 16px;
43 |   color: black !important;
44 | }
45 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/webview/bing.scss:
--------------------------------------------------------------------------------
 1 | #b_header,
 2 | #b_context,
 3 | #mfa_root,
 4 | #ev_talkbox_wrapper,
 5 | #b_footer,
 6 | .b_ans,
 7 | .b_rs {
 8 |   display: none;
 9 |   height: 0 !important;
10 |   width: 0 !important;
11 |   max-width: 0 !important;
12 |   min-width: 0 !important;
13 | }
14 | 
15 | body,
16 | #b_content {
17 |   width: 500px !important;
18 |   max-width: 500px !important;
19 |   min-width: 500px !important;
20 |   margin: 0 0 0 0px !important;
21 |   padding: 0 !important;
22 | }
23 | 
24 | .b_algo {
25 |   box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
26 |   transition: 0.3s;
27 |   border-radius: 5px;
28 |   padding: 10px 16px;
29 |   margin: 10px 0 10px 5px !important;
30 | }
31 | 
32 | .b_algo:hover {
33 |   box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
34 | }
35 | 
36 | a > strong {
37 |   font-size: 16px;
38 |   color: red !important;
39 | }
40 | 
41 | a:not(strong) {
42 |   font-size: 16px;
43 |   color: black !important;
44 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/webview/google.css:
--------------------------------------------------------------------------------
 1 | #sfcnt,
 2 | #tsf,
 3 | .liYKde,
 4 | .sfbg,
 5 | .g-blk,
 6 | #searchform,
 7 | #botstuff,
 8 | #footcnt,
 9 | .ULSxyf,
10 | #top_nav {
11 |   display: none;
12 |   height: 0 !important;
13 |   width: 0 !important;
14 |   max-width: 0 !important;
15 |   min-width: 0 !important;
16 | }
17 | 
18 | #main,
19 | #cnt,
20 | #center_col,
21 | body,
22 | html {
23 |   width: 500px !important;
24 |   max-width: 500px !important;
25 |   min-width: 500px !important;
26 |   margin: 0 0 0 2px !important;
27 |   padding: 0 !important;
28 | }
29 | 
30 | .page-inner {
31 |   background-color: #FFF;
32 |   padding: 0 0 10px 0 !important;
33 | }
34 | 
35 | .g {
36 |   box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
37 |   transition: 0.3s;
38 |   border-radius: 5px;
39 |   padding: 10px 16px;
40 |   margin: 10px 0 10px 5px !important;
41 | }
42 | 
43 | .g:hover {
44 |   box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
45 | }
46 | 
47 | h3 {
48 |   font-size: 16px !important;
49 | }
50 | 
51 | a > strong {
52 |   font-size: 16px;
53 |   color: red !important;
54 | }
55 | 
56 | a:not(strong) {
57 |   font-size: 16px;
58 |   color: black !important;
59 | }
60 | 
61 | a,
62 | em {
63 |   text-decoration: none !important;
64 | }
65 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/home/webview/google.scss:
--------------------------------------------------------------------------------
 1 | #sfcnt,
 2 | #tsf,
 3 | .liYKde,
 4 | .sfbg,
 5 | .g-blk,
 6 | #searchform,
 7 | #botstuff,
 8 | #footcnt,
 9 | .ULSxyf,
10 | #top_nav {
11 |   display: none;
12 |   height: 0 !important;
13 |   width: 0 !important;
14 |   max-width: 0 !important;
15 |   min-width: 0 !important;
16 | }
17 | 
18 | #main,
19 | #cnt,
20 | #center_col,
21 | body,
22 | html {
23 |   width: 500px !important;
24 |   max-width: 500px !important;
25 |   min-width: 500px !important;
26 |   margin: 0 0 0 2px !important;
27 |   padding: 0 !important;
28 | }
29 | 
30 | .page-inner {
31 |   background-color: #FFF;
32 |   padding: 0 0 10px 0 !important;
33 | }
34 | 
35 | .g {
36 |   box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
37 |   transition: 0.3s;
38 |   border-radius: 5px;
39 |   padding: 10px 16px;
40 |   margin: 10px 0 10px 5px !important;
41 | }
42 | 
43 | 
44 | .g:hover {
45 |   box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
46 | }
47 | 
48 | h3 {
49 |   font-size: 16px !important;
50 | }
51 | 
52 | a > strong {
53 |   font-size: 16px;
54 |   color: red !important;
55 | }
56 | 
57 | a:not(strong) {
58 |   font-size: 16px;
59 |   color: black !important;
60 | }
61 | 
62 | a,
63 | em {
64 |   text-decoration: none !important;
65 | }


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/custom.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/_global.scss";
 2 | 
 3 | $base: #FFF;
 4 | $theme-bg-color: $base;
 5 | 
 6 | $theme-color: custom-theme-color;
 7 | $theme-color-dark: $md_grey-600;
 8 | $theme-color-light: rgba(59, 66, 82, 0.2);
 9 | $theme-hover-color: $md_grey_400;
10 | $theme-icon-hover-color: $theme-color;
11 | $theme-border-color: rgb(224, 224, 224);
12 | $sidebar-bg-color: $theme-bg-color;
13 | $header-bg-color: $theme-color;
14 | 
15 | @import "theme";


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/darcula.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(43, 43, 43);
5 | $header-bg-color: $theme-color;
6 | $theme-color-light: rgba(50, 50, 50, 0.2);
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/dark.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/_global.scss";
 2 | @import "default";
 3 | 
 4 | $base: rgb(43, 43, 43);
 5 | $theme-bg-color: $base;
 6 | 
 7 | $theme-color: rgb(51, 51, 51);
 8 | $theme-color-dark: $md_grey-600;
 9 | $theme-color-light: rgba(224, 224, 224, 0.2);
10 | $theme-hover-color: rgb(51, 51, 51);
11 | $theme-border-color: rgb(67, 67, 67);
12 | $theme-icon-hover-color: #FFF;
13 | $sidebar-bg-color: $theme-bg-color;
14 | $header-bg-color: rgb(35, 35, 38);
15 | 
16 | @import "theme";
17 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/default.css:
--------------------------------------------------------------------------------
 1 | .root {
 2 |   -fx-base: #FFF;
 3 |   theme-bg-color: #FFF;
 4 |   theme-color: #3b4252;
 5 |   theme-color-light: rgba(59, 66, 82, 0.2);
 6 |   theme-color-dark: #757575;
 7 |   theme-border-color: rgb(224, 224, 224);
 8 |   theme-hover-color: #bdbdbd;
 9 |   theme-icon-hover-color: #3b4252;
10 | }
11 | 
12 | .stage-decorator {
13 |   -fx-bg-color: #FFF;
14 |   -fx-header-bg-color: #3b4252;
15 | }
16 | .stage-decorator .sidebar-nav {
17 |   -fx-sidebar-nav-bg-color: #FFF;
18 |   -fx-sidebar-bg-color: #FFF;
19 |   -fx-sidebar-border-color: rgb(224, 224, 224);
20 |   -fx-menu-bg-color-selected: #3b4252;
21 |   -fx-menu-bg-color-hover: #bdbdbd;
22 | }
23 | 
24 | .jfx-alert-overlay {
25 |   -fx-bg-color: #FFF;
26 | }
27 | 
28 | .loading {
29 |   -fx-bg-color: #FFF;
30 | }
31 | 
32 | .scroll-bar {
33 |   -fx-color: rgba(59, 66, 82, 0.2);
34 |   -fx-hover-color: #757575;
35 | }
36 | 
37 | .jfx-slider {
38 |   -jfx-default-thumb: #3b4252;
39 |   -jfx-default-track: rgba(59, 66, 82, 0.2);
40 | }
41 | 
42 | .check-box {
43 |   -fx-checked-color: #3b4252;
44 |   -fx-unchecked-color: #757575;
45 | }
46 | 
47 | .list-view {
48 |   -fx-selected-color: #757575;
49 |   -fx-separator-color: rgba(59, 66, 82, 0.2);
50 | }
51 | 
52 | .tab-button {
53 |   -fx-selected-color: #3b4252;
54 |   -fx-color: rgba(59, 66, 82, 0.2);
55 | }
56 | 
57 | .jfx-tab-pane {
58 |   -fx-color: #3b4252;
59 | }
60 | 
61 | .text-field,
62 | .text-area,
63 | .input-box,
64 | .spinner,
65 | .loading-image-view {
66 |   -fx-bg-color: rgba(59, 66, 82, 0.2);
67 | }
68 | 
69 | .progress-bar {
70 |   -fx-color: #3b4252;
71 |   -fx-bg-color: rgba(59, 66, 82, 0.2);
72 | }
73 | 
74 | .tag {
75 |   -fx-tag-color: #3b4252;
76 | }
77 | 
78 | .context-menu {
79 |   -fx-selected-color: #3b4252;
80 | }
81 | 
82 | .combo-box-base {
83 |   -fx-bg-color: rgba(59, 66, 82, 0.2);
84 |   -fx-selected-color: #3b4252;
85 | }
86 | 
87 | .reader-container {
88 |   -fx-bg-color: #3b4252;
89 | }
90 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/default.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/_global.scss";
 2 | 
 3 | $base: #FFF;
 4 | $theme-bg-color: $base;
 5 | 
 6 | $theme-color: #3b4252;
 7 | $theme-color-dark: $md_grey-600;
 8 | $theme-color-light: rgba(59, 66, 82, 0.2);
 9 | $theme-hover-color: $md_grey_400;
10 | $theme-icon-hover-color: $theme-color;
11 | $theme-border-color: rgb(224, 224, 224);
12 | $sidebar-bg-color: $theme-bg-color;
13 | $header-bg-color: $theme-color;
14 | 
15 | @import "theme";


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/green.scss:
--------------------------------------------------------------------------------
 1 | @import "../../define/_global.scss";
 2 | @import "default";
 3 | 
 4 | // 按钮选中颜色
 5 | $theme-color: #ceebce;
 6 | $theme-color-light: rgba(77, 175, 124, 0.2);
 7 | $header-bg-color: $theme-color;
 8 | $theme-icon-hover-color: $theme-color;
 9 | @import "theme";
10 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/grey.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(108, 122, 137);
5 | $header-bg-color: $theme-color;
6 | $theme-color-light: rgba(108, 122, 137, 0.2);
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/pink.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: #eec5cb;
5 | $header-bg-color: $theme-color;
6 | $theme-color-light: rgba(214, 130, 141, 0.2);
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/teal.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: rgb(51, 110, 123);
5 | $theme-color-light: rgba(51, 110, 123, 0.2);
6 | $header-bg-color: $theme-color;
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/white.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: #F0F0F0;
5 | $header-bg-color: $theme-color;
6 | $theme-color-light: rgba(250, 246, 246, 0.48);
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/css/reader/theme/yellow.scss:
--------------------------------------------------------------------------------
1 | @import "../../define/_global.scss";
2 | @import "default";
3 | 
4 | $theme-color: #e6dbbf;
5 | $theme-color-light: rgba(233, 212, 96, 0.2);
6 | $header-bg-color: $theme-color;
7 | $theme-icon-hover-color: $theme-color;
8 | @import "theme";
9 | 


--------------------------------------------------------------------------------
/app/src/main/resources/layout/home/download-manager.fxml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | 
 3 | <?import com.unclezs.novel.app.framework.components.PlaceHolder?>
 4 | <?import com.unclezs.novel.app.framework.components.TabButton?>
 5 | <?import com.unclezs.novel.app.framework.components.TabGroup?>
 6 | <?import com.unclezs.novel.app.framework.components.TitleBar?>
 7 | <?import javafx.scene.control.TableView?>
 8 | <?import javafx.scene.layout.HBox?>
 9 | <?import javafx.scene.layout.StackPane?>
10 | <?import javafx.scene.layout.VBox?>
11 | <StackPane xmlns="https://javafx.com/javafx" styleClass="download-manager" xmlns:fx="http://javafx.com/fxml" fx:controller="com.unclezs.novel.app.main.views.home.DownloadManagerView">
12 |   <VBox styleClass="container">
13 |     <TitleBar title="%home.menu.download.management">
14 |       <content>
15 |         <HBox>
16 |           <TabButton text="%download.manager.running" fx:id="tasksTab" styleClass="tasks-tab" selected="true" icon="download">
17 |             <tabGroup>
18 |               <TabGroup fx:id="tabs"/>
19 |             </tabGroup>
20 |           </TabButton>
21 |           <TabButton text="%download.manager.history" fx:id="historyTab" tabGroup="$tabs" styleClass="history-tab" icon="download_history"/>
22 |         </HBox>
23 |       </content>
24 |     </TitleBar>
25 |     <StackPane fx:id="container" VBox.vgrow="ALWAYS">
26 |       <VBox fx:id="tasksPanel" spacing="2">
27 |         <TableView fx:id="tasksTable" VBox.vgrow="ALWAYS">
28 |           <placeholder>
29 |             <PlaceHolder tip="%download.manager.placeholder"/>
30 |           </placeholder>
31 |         </TableView>
32 |       </VBox>
33 |     </StackPane>
34 |   </VBox>
35 | </StackPane>
36 | 


--------------------------------------------------------------------------------
/app/src/main/resources/layout/home/header-menu.fxml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | 
 3 | <?import com.jfoenix.controls.JFXPopup?>
 4 | <?import com.unclezs.novel.app.framework.components.icon.IconButton?>
 5 | <?import javafx.scene.layout.Pane?>
 6 | <?import javafx.scene.layout.VBox?>
 7 | <JFXPopup xmlns:fx="http://javafx.com/fxml" xmlns="https://javafx.com/javafx" fx:controller="com.unclezs.novel.app.main.views.home.HeaderMenuView"
 8 |   fx:id="popup">
 9 |   <VBox spacing="10" styleClass="home-setting-popup">
10 |     <IconButton icon="github" text="GitHub源码" onAction="#github"/>
11 |     <Pane styleClass="home-setting-popup-delimiter"/>
12 |     <IconButton icon="official_site" text="访问官网" onAction="#officialSite"/>
13 |     <IconButton icon="reward" text="赞助开发" onAction="#reward"/>
14 |     <IconButton icon="doc" text="使用教程" onAction="#doc"/>
15 |     <IconButton icon="confirm" text="常见问题" onAction="#questions"/>
16 |     <IconButton icon="log" text="查看日志" onAction="#showLog"/>
17 |     <IconButton icon="feedback" text="问题反馈" onAction="#feedback"/>
18 |     <Pane styleClass="home-setting-popup-delimiter"/>
19 |     <IconButton icon="statement" text="免责声明" onAction="#statement"/>
20 |     <IconButton icon="about" text="关于" onAction="#about"/>
21 |   </VBox>
22 | </JFXPopup>
23 | 


--------------------------------------------------------------------------------
/app/src/main/resources/layout/home/search-novel.fxml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | 
 3 | 
 4 | <?import com.jfoenix.controls.JFXProgressBar?>
 5 | <?import com.unclezs.novel.app.framework.components.icon.Icon?>
 6 | <?import com.unclezs.novel.app.framework.components.icon.IconButton?>
 7 | <?import com.unclezs.novel.app.framework.components.SearchBar?>
 8 | <?import com.unclezs.novel.app.framework.components.TitleBar?>
 9 | <?import javafx.geometry.Insets?>
10 | <?import javafx.scene.control.Label?>
11 | <?import javafx.scene.control.ListView?>
12 | <?import javafx.scene.layout.StackPane?>
13 | <?import javafx.scene.layout.VBox?>
14 | <StackPane xmlns:fx="http://javafx.com/fxml/1" xmlns="https://javafx.com/javafx" styleClass="search-novel" fx:controller="com.unclezs.novel.app.main.views.home.SearchNovelView">
15 |   <VBox styleClass="container">
16 |     <TitleBar title="%home.menu.search.internal">
17 |       <content>
18 |         <SearchBar onSearch="#search" fx:id="searchBar"/>
19 |       </content>
20 |     </TitleBar>
21 |     <JFXProgressBar maxWidth="Infinity" maxHeight="1" fx:id="loadingBar" visible="false">
22 |       <VBox.margin>
23 |         <Insets top="10"/>
24 |       </VBox.margin>
25 |     </JFXProgressBar>
26 |     <ListView VBox.vgrow="ALWAYS" fx:id="listView" styleClass="result-list">
27 |       <placeholder>
28 |         <VBox alignment="CENTER" styleClass="placeholder">
29 |           <Icon value="empty"/>
30 |           <Label styleClass="tip" text="%home.search.novel.placeholder"/>
31 |         </VBox>
32 |       </placeholder>
33 |     </ListView>
34 |   </VBox>
35 |   <IconButton StackPane.alignment="BOTTOM_RIGHT" visible="false" managed="false" fx:id="loading" icon="stop_loading" styleClass="stop-search" pickOnBounds="false">
36 |     <StackPane.margin>
37 |       <Insets bottom="30" right="30"/>
38 |     </StackPane.margin>
39 |   </IconButton>
40 | </StackPane>
41 | 


--------------------------------------------------------------------------------
/app/src/main/resources/layout/home/theme.fxml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | 
 3 | <?import com.jfoenix.controls.JFXPopup?>
 4 | <?import com.unclezs.novel.app.framework.components.icon.IconButton?>
 5 | <?import javafx.scene.layout.HBox?>
 6 | <?import javafx.scene.layout.VBox?>
 7 | <JFXPopup xmlns:fx="http://javafx.com/fxml" xmlns="https://javafx.com/javafx" fx:controller="com.unclezs.novel.app.main.views.home.ThemeView">
 8 |   <VBox spacing="10" styleClass="home-theme-popup" fx:id="box">
 9 |     <HBox styleClass="theme-popup-color-box">
10 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,default" userData="default"/>
11 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,dark" userData="dark"/>
12 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,green" userData="green"/>
13 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,red" userData="red"/>
14 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,blue" userData="blue"/>
15 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,pink" userData="pink"/>
16 |     </HBox>
17 |     <HBox styleClass="theme-popup-color-box">
18 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,purple" userData="purple"/>
19 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,deep-blue" userData="deep-blue"/>
20 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,light-blue" userData="light-blue"/>
21 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,teal" userData="teal"/>
22 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,orange" userData="orange"/>
23 |       <IconButton onMouseClicked="#changeTheme" styleClass="color-box-item,grey" userData="grey"/>
24 |     </HBox>
25 |   </VBox>
26 | </JFXPopup>
27 | 


--------------------------------------------------------------------------------
/app/src/main/resources/layout/reader/setting.fxml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | 
 3 | <?import com.jfoenix.controls.JFXPopup?>
 4 | <?import com.unclezs.novel.app.framework.components.icon.IconButton?>
 5 | <?import javafx.scene.layout.Pane?>
 6 | <?import javafx.scene.layout.VBox?>
 7 | <JFXPopup xmlns:fx="http://javafx.com/fxml" xmlns="https://javafx.com/javafx" fx:controller="com.unclezs.novel.app.main.views.home.HeaderMenuView"
 8 |   fx:id="popup">
 9 |   <VBox spacing="10" styleClass="home-setting-popup">
10 |     <IconButton icon="&#xf092;" text="GitHub源码"/>
11 |     <IconButton icon="&#xf013;" text="设置"/>
12 |     <IconButton icon="&#xf23b;" text="打赏"/>
13 |     <IconButton icon="&#xf170;" text="关于"/>
14 |     <IconButton icon="&#xf170;" text="更新历史"/>
15 |     <IconButton icon="&#xf1da;" text="查看日志"/>
16 |     <IconButton icon="&#xf0f6;" text="问题反馈"/>
17 |     <IconButton icon="&#xf188;" text="免责声明"/>
18 |     <Pane styleClass="home-setting-popup-delimiter"/>
19 |     <Pane styleClass="home-setting-popup-delimiter"/>
20 |   </VBox>
21 | </JFXPopup>
22 | 


--------------------------------------------------------------------------------
/app/src/main/resources/templates/epub/META-INF/container.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0"?>
2 | <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
3 |   <rootfiles>
4 |     <rootfile full-path="content.opf" media-type="application/oebps-package+xml"/>
5 | 
6 |   </rootfiles>
7 | </container>
8 | 


--------------------------------------------------------------------------------
/app/src/main/resources/templates/epub/content.opf.vm:
--------------------------------------------------------------------------------
 1 | <?xml version='1.0' encoding='utf-8'?>
 2 | <package xmlns="http://www.idpf.org/2007/opf" unique-identifier="uuid_id" version="2.0">
 3 |   <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
 4 |     <meta name="cover" content="cover"/>
 5 |     <dc:title>$data.title</dc:title>
 6 |     <dc:identifier opf:scheme="uuid" id="uncle-novel-uuid">$uuid.randomUUID()</dc:identifier>
 7 |     <dc:creator>Uncle小说</dc:creator>
 8 |     <dc:publisher>公众号:书虫无书荒</dc:publisher>
 9 |     <dc:language>zh-cn</dc:language>
10 |   </metadata>
11 |   <manifest>
12 |     <item id="cover" media-type="image/jpeg" href="cover.jpeg"></item>
13 |     <item id="ncx" media-type="application/x-dtbncx+xml" href="toc.ncx"></item>
14 |     <item id="toc" media-type="application/xhtml+xml" href="text/toc.html"></item>
15 |     <item href="style/style.css" id="page_css" media-type="text/css"></item>
16 |       #foreach($chapter in $data.chapters)
17 |           #if($chapter.state == "DOWNLOADED")
18 |             <item id="chapter_${chapter.order}" media-type="application/xhtml+xml" href="text/${chapter.order}.html"/>
19 |           #end
20 |       #end
21 |   </manifest>
22 | 
23 |   <spine toc="ncx">
24 |     <itemref idref="toc"/>
25 |       #foreach($chapter in $data.chapters)
26 |           #if($chapter.state == "DOWNLOADED")
27 |             <itemref idref="chapter_${chapter.order}"/>
28 |           #end
29 |       #end
30 |   </spine>
31 | 
32 |   <guide>
33 |     <reference type="text" title="正文" href="text/1.html"></reference>
34 |     <reference type="toc" title="目录" href="text/toc.html"></reference>
35 |   </guide>
36 | 
37 | </package>
38 | 
39 | 


--------------------------------------------------------------------------------
/app/src/main/resources/templates/epub/mimetype:
--------------------------------------------------------------------------------
1 | application/epub+zip


--------------------------------------------------------------------------------
/app/src/main/resources/templates/epub/style/style.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/app/src/main/resources/templates/epub/style/style.css


--------------------------------------------------------------------------------
/app/src/main/resources/templates/epub/text/chapter.html.vm:
--------------------------------------------------------------------------------
 1 | #* @vtlvariable name="data" type="com.unclezs.novel.analyzer.model.Chapter" *#
 2 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
 3 | <head>
 4 |   <title>${data.name}</title>
 5 |   <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 6 |   <link rel="stylesheet" type="text/css" href="../style/style.css"/>
 7 | </head>
 8 | <body>
 9 | <h2>${data.name}</h2>
10 | <div class="chapter">
11 |     #foreach($paragraph in $data.content.split("\n"))
12 |       <p>$paragraph</p>
13 |     #end
14 | </div>
15 | </body>
16 | </html>
17 | 


--------------------------------------------------------------------------------
/app/src/main/resources/templates/epub/text/toc.html.vm:
--------------------------------------------------------------------------------
 1 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
 2 | <head>
 3 |   <title>${data.title}</title>
 4 |   <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 5 |   <link rel="stylesheet" type="text/css" href="../style/style.css"/>
 6 | </head>
 7 | <body>
 8 | <h2>${data.title}</h2>
 9 | <div class="toc">
10 |     #foreach($chapter in $data.chapters)
11 |         #if($chapter.state == "DOWNLOADED")
12 |           <div><a href="${chapter.order}.html">${chapter.name}</a></div>
13 |         #end
14 |     #end
15 | </div>
16 | </body>
17 | </html>
18 | 


--------------------------------------------------------------------------------
/app/src/main/resources/templates/epub/toc.ncx.vm:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
 3 | <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="zh-cn">
 4 |   <head>
 5 |     <meta name="dtb:uid" content="79825d33-641e-4235-8e1b-596c7d57c94a"/>
 6 |     <meta name="dtb:depth" content="1"/>
 7 |     <meta name="dtb:totalPageCount" content="0"/>
 8 |     <meta name="dtb:maxPageNumber" content="0"/>
 9 |     <meta name="dtb:generator" content=" "/>
10 |   </head>
11 |   <docTitle>
12 |     <text>${data.title}</text>
13 |   </docTitle>
14 |   <docAuthor>
15 |     <text>${data.author} </text>
16 |   </docAuthor>
17 |   <navMap>
18 |     <navPoint id="toc" playOrder="-1">
19 |       <navLabel>
20 |         <text>目录:${data.title}</text>
21 |       </navLabel>
22 |       <content src="text/toc.html"></content>
23 |     </navPoint>
24 |       #foreach($chapter in $data.chapters)
25 |           #if($chapter.state == "DOWNLOADED")
26 |             <navPoint id="chapter_${chapter.order}" playOrder="${chapter.order}">
27 |               <navLabel>
28 |                 <text>${chapter.name}</text>
29 |               </navLabel>
30 |               <content src="text/${chapter.order}.html"/>
31 |             </navPoint>
32 |           #end
33 |       #end
34 |   </navMap>
35 | </ncx>
36 | 


--------------------------------------------------------------------------------
/app/src/main/resources/templates/epub/velocity_implicit.vm:
--------------------------------------------------------------------------------
1 | #* @implicitly included *#
2 | #* @vtlvariable name="chapter" type="com.unclezs.novel.analyzer.model.Chapter" *#
3 | #* @vtlvariable name="uuid" type="java.util.UUID" *#
4 | #* @vtlvariable name="data" type="com.unclezs.novel.analyzer.model.Novel" *#
5 | 


--------------------------------------------------------------------------------
/buildSrc/build.gradle:
--------------------------------------------------------------------------------
 1 | buildscript {
 2 |     repositories {
 3 |         mavenCentral()
 4 |         maven { url("https://oss.sonatype.org/content/groups/public/") }
 5 |     }
 6 | }
 7 | plugins {
 8 |     id "java-gradle-plugin"
 9 |     id "io.spring.dependency-management" version "1.0.9.RELEASE" apply false
10 | }
11 | apply from: "../gradle/dependency-management.gradle"
12 | repositories {
13 |     maven { url("https://oss.sonatype.org/content/groups/public/") }
14 |     mavenCentral()
15 |     gradlePluginPortal()
16 | }
17 | 
18 | gradlePlugin {
19 |     plugins {
20 |         "app-packager" {
21 |             id = 'app-packager'
22 |             implementationClass = 'com.unclezs.novel.app.packager.PackagePlugin'
23 |         }
24 | 
25 |         "javafxPlugin" {
26 |             id = 'org.openjfx.javafxplugin'
27 |             implementationClass = 'org.openjfx.gradle.JavaFXPlugin'
28 |         }
29 |     }
30 | }
31 | 
32 | dependencies {
33 |     annotationProcessor "org.projectlombok:lombok"
34 |     compileOnly "org.projectlombok:lombok"
35 | 
36 |     implementation "com.unclezs:jfx-launcher"
37 |     implementation "com.google.code.gson:gson"
38 | 
39 |     implementation "cn.hutool:hutool-all"
40 | 
41 |     // packager
42 |     implementation 'org.apache.velocity:velocity-engine-core'
43 |     implementation 'com.netflix.nebula:gradle-ospackage-plugin'
44 |     implementation 'net.jsign:jsign-core'
45 |     implementation 'com.google.gradle:osdetector-gradle-plugin'
46 |     implementation 'org.vafer:jdeb:1.9'
47 | 
48 |     // jfx
49 |     implementation 'com.google.gradle:osdetector-gradle-plugin:1.7.3'
50 |     implementation 'org.javamodularity:moduleplugin:1.8.12'
51 | }
52 | 


--------------------------------------------------------------------------------
/buildSrc/settings.gradle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/buildSrc/settings.gradle


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/Context.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager;
 2 | 
 3 | import com.unclezs.novel.app.packager.packager.AbstractPackager;
 4 | import lombok.experimental.UtilityClass;
 5 | import org.gradle.api.Project;
 6 | import org.gradle.api.logging.Logger;
 7 | 
 8 | /**
 9 |  * Gradle打包 构建工具上下文
10 |  *
11 |  * @author blog.unclezs.com
12 |  * @since 2021/3/28 23:07
13 |  */
14 | @UtilityClass
15 | public class Context {
16 | 
17 |   public static Project project;
18 |   public static AbstractPackager packager;
19 | 
20 |   public static Logger getLogger() {
21 |     return project.getLogger();
22 |   }
23 | }
24 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/PackagePlugin.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager;
 2 | 
 3 | import com.unclezs.novel.app.packager.model.PackagerExtension;
 4 | import com.unclezs.novel.app.packager.task.UpgradeTask;
 5 | import org.gradle.api.NonNullApi;
 6 | import org.gradle.api.Plugin;
 7 | import org.gradle.api.Project;
 8 | 
 9 | /**
10 |  * Java 打平台包插件
11 |  *
12 |  * @author https://github.com/fvarrui/JavaPackager
13 |  * @author blog.unclezs.com
14 |  * @since 2021/03/23 19:10
15 |  */
16 | @NonNullApi
17 | public class PackagePlugin implements Plugin<Project> {
18 | 
19 |   public static final String GROUP_NAME = "packager";
20 |   public static final String EXTENSION_NAME = "packager";
21 | 
22 |   @Override
23 |   public void apply(Project project) {
24 |     Context.project = project;
25 |     project.getExtensions().create(GROUP_NAME, PackagerExtension.class, project);
26 |     project.getTasks().create("upgrade", UpgradeTask.class).dependsOn("build");
27 |   }
28 | }
29 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/exception/PackageException.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager.exception;
 2 | 
 3 | /**
 4 |  * 打包异常
 5 |  *
 6 |  * @author blog.unclezs.com
 7 |  * @since 2021/4/10 9:51 上午
 8 |  */
 9 | public class PackageException extends RuntimeException {
10 |   public PackageException() {
11 |   }
12 | 
13 |   public PackageException(String message) {
14 |     super(message);
15 |   }
16 | 
17 |   public PackageException(String message, Throwable cause) {
18 |     super(message, cause);
19 |   }
20 | 
21 |   public PackageException(Throwable cause) {
22 |     super(cause);
23 |   }
24 | 
25 |   public PackageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
26 |     super(message, cause, enableSuppression, writableStackTrace);
27 |   }
28 | }
29 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/model/LinuxConfig.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager.model;
 2 | 
 3 | import java.io.File;
 4 | import java.io.Serializable;
 5 | import lombok.Data;
 6 | import lombok.EqualsAndHashCode;
 7 | 
 8 | /**
 9 |  * linux 配置
10 |  *
11 |  * @author blog.unclezs.com
12 |  * @since 2021/4/2 1:29
13 |  */
14 | @Data
15 | @EqualsAndHashCode(callSuper = true)
16 | public class LinuxConfig extends PlatformConfig implements Serializable {
17 | 
18 |   private static final long serialVersionUID = -1238166997019141904L;
19 |   private boolean generateDeb = true;
20 |   private boolean generateRpm = true;
21 |   private File pngFile;
22 |   private File xpmFile;
23 | }
24 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/model/PlatformConfig.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager.model;
 2 | 
 3 | import java.io.File;
 4 | import lombok.Getter;
 5 | import lombok.Setter;
 6 | import lombok.ToString;
 7 | 
 8 | /**
 9 |  * 平台配置
10 |  *
11 |  * @author blog.unclezs.com
12 |  * @since 2021/04/02 1:27
13 |  */
14 | @Getter
15 | @Setter
16 | @ToString
17 | public class PlatformConfig {
18 | 
19 |   /**
20 |    * 图标文件
21 |    */
22 |   protected File iconFile;
23 | }
24 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/model/WinConfig.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager.model;
 2 | 
 3 | import java.io.Serializable;
 4 | import java.util.LinkedHashMap;
 5 | import java.util.UUID;
 6 | import lombok.Data;
 7 | import lombok.EqualsAndHashCode;
 8 | import lombok.ToString;
 9 | 
10 | /**
11 |  * Windows特定配置
12 |  *
13 |  * @author blog.unclezs.com
14 |  * @since 2021/3/29 0:32
15 |  */
16 | @Data
17 | @ToString(callSuper = true)
18 | @EqualsAndHashCode(callSuper = true)
19 | public class WinConfig extends PlatformConfig implements Serializable {
20 | 
21 |   private static final long serialVersionUID = 2106752412224694318L;
22 |   /**
23 |    * 是否把 runnable jar 打包进exe
24 |    */
25 |   private Boolean wrapJar = true;
26 |   /**
27 |    * version info
28 |    */
29 |   private String internalName;
30 |   private String companyName;
31 |   private String copyright;
32 |   private String fileVersion;
33 |   private String productVersion;
34 |   private String fileDescription;
35 | 
36 |   /**
37 |    * inno setup参数,需要inno setup v6+
38 |    * <p>
39 |    * 选择自定义安装位置
40 |    */
41 |   private Boolean showSelectInstallDirPage = true;
42 |   /**
43 |    * 显示开始菜单文件夹选择页面
44 |    */
45 |   private Boolean showSelectedProgramGroupPage = false;
46 |   /**
47 |    * 显示安装完成页面
48 |    */
49 |   private Boolean showFinishedPage = true;
50 |   /**
51 |    * 创建桌面图标
52 |    */
53 |   private Boolean createDesktopIconTask = true;
54 |   /**
55 |    * 安装的语言
56 |    * <p>
57 |    * https://jrsoftware.org/files/istrans/
58 |    */
59 |   private LinkedHashMap<String, String> setupLanguages = new LinkedHashMap<>();
60 |   /**
61 |    * 安装类型 installForAllUsers/installForCurrentUser
62 |    */
63 |   private String setupMode = "installForCurrentUser";
64 | 
65 |   private Boolean generateSetup = true;
66 |   private Boolean generateMsi = false;
67 |   private Boolean generateMsm = false;
68 |   private String msiUpgradeCode = UUID.randomUUID().toString();
69 |   private WindowsSigning signing;
70 | }
71 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/model/WindowsSigning.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager.model;
 2 | 
 3 | import java.io.File;
 4 | import lombok.Data;
 5 | 
 6 | /**
 7 |  * Info needed for signing EXEs on Windows
 8 |  */
 9 | @Data
10 | public class WindowsSigning {
11 | 
12 |   private String storetype;
13 |   private File keystore;
14 |   private File certfile;
15 |   private File keyfile;
16 |   private String storepass;
17 |   private String alias;
18 |   private String keypass;
19 |   private String alg;
20 | }
21 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/package-info.java:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * 这个模块是基于 <a href="https://github.com/fvarrui/JavaPackager">JavaPackager</a>开源项目根据需求修改的
 3 |  * <p>
 4 |  * 感谢 @fvarrui
 5 |  *
 6 |  * @author blog.unclezs.com
 7 |  * @since 2021/4/7 11:30
 8 |  */
 9 | package com.unclezs.novel.app.packager;
10 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/subtask/BaseSubTask.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager.subtask;
 2 | 
 3 | import com.unclezs.novel.app.packager.Context;
 4 | import com.unclezs.novel.app.packager.exception.PackageException;
 5 | import com.unclezs.novel.app.packager.packager.AbstractPackager;
 6 | import com.unclezs.novel.app.packager.util.Logger;
 7 | import lombok.Getter;
 8 | import org.gradle.api.Project;
 9 | 
10 | /**
11 |  * 子任务基类
12 |  *
13 |  * @author blog.unclezs.com
14 |  * @since 2021/04/01 16:56
15 |  */
16 | public abstract class BaseSubTask {
17 | 
18 |   /**
19 |    * 打包器
20 |    */
21 |   protected AbstractPackager packager;
22 |   /**
23 |    * 项目
24 |    */
25 |   protected Project project;
26 |   /**
27 |    * 任务名称
28 |    */
29 |   @Getter
30 |   protected String name;
31 | 
32 |   public BaseSubTask() {
33 |     this.packager = Context.packager;
34 |     this.project = Context.project;
35 |   }
36 | 
37 |   public BaseSubTask(String name) {
38 |     this();
39 |     this.name = name;
40 |   }
41 | 
42 |   /**
43 |    * 实现核心逻辑
44 |    *
45 |    * @return 结果
46 |    * @throws Exception 错误
47 |    */
48 |   protected abstract Object run() throws Exception;
49 | 
50 |   /**
51 |    * 是启用执行
52 |    *
53 |    * @return true 启用执行
54 |    */
55 |   protected boolean enabled() {
56 |     return true;
57 |   }
58 | 
59 |   /**
60 |    * 真正执行任务
61 |    */
62 |   @SuppressWarnings("unchecked")
63 |   public <T> T apply() {
64 |     Object result = null;
65 |     if (enabled()) {
66 |       Logger.infoIndent("开始{}...", name);
67 |       try {
68 |         result = run();
69 |       } catch (Exception ex) {
70 |         Logger.error("{}失败!", name, ex);
71 |         throw new PackageException(ex);
72 |       }
73 |       if (result == null) {
74 |         Logger.infoUnIndent("成功{}!", name);
75 |       } else {
76 |         Logger.infoUnIndent("成功{}: {}", name, result);
77 |       }
78 |     }
79 |     return (T) result;
80 |   }
81 | }
82 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/subtask/CreateRunnableJar.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager.subtask;
 2 | 
 3 | import java.io.File;
 4 | import org.gradle.api.tasks.bundling.Jar;
 5 | 
 6 | /**
 7 |  * 创建可运行的jar文件
 8 |  *
 9 |  * @author https://github.com/fvarrui/JavaPackager
10 |  * @author blog.unclezs.com
11 |  * @since 2021/03/24 0:06
12 |  */
13 | public class CreateRunnableJar extends BaseSubTask {
14 | 
15 |   public CreateRunnableJar() {
16 |     super("创建可执行Jar包");
17 |   }
18 | 
19 |   @Override
20 |   protected File run() {
21 |     Jar jarTask = (Jar) project.getTasks().getByName("jar");
22 |     jarTask.getManifest().getAttributes().put("Main-Class", packager.getMainClass());
23 |     jarTask.getActions().forEach(action -> action.execute(jarTask));
24 |     // classpath 增加依赖目录
25 |     packager.getClasspath().add(packager.getLibsFolderPath());
26 |     return jarTask.getArchiveFile().get().getAsFile();
27 |   }
28 | }
29 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/subtask/linux/GenerateRpm.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager.subtask.linux;
 2 | 
 3 | import com.unclezs.novel.app.packager.subtask.BaseSubTask;
 4 | 
 5 | /**
 6 |  * Creates a RPM package file including all app folder's content only for GNU/Linux so app could be easily distributed on Gradle context
 7 |  */
 8 | public class GenerateRpm extends BaseSubTask {
 9 | 
10 |   public GenerateRpm() {
11 |     super("RPM package");
12 |   }
13 | 
14 |   @Override
15 |   protected boolean enabled() {
16 |     return packager.getLinuxConfig().isGenerateRpm();
17 |   }
18 | 
19 |   @Override
20 |   protected Object run() throws Exception {
21 |     return null;
22 |   }
23 | }
24 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/subtask/mac/GeneratePkg.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager.subtask.mac;
 2 | 
 3 | import cn.hutool.core.util.StrUtil;
 4 | import com.unclezs.novel.app.packager.packager.MacPackager;
 5 | import com.unclezs.novel.app.packager.subtask.BaseSubTask;
 6 | import com.unclezs.novel.app.packager.util.ExecUtils;
 7 | 
 8 | import java.io.File;
 9 | 
10 | /**
11 |  * 创建一个PKG安装程序文件,其中包含所有应用程序文件夹的内容,仅适用于MacOS,这样应用程序就可以轻松分发
12 |  *
13 |  * @author https://github.com/fvarrui/JavaPackager
14 |  * @author blog.unclezs.com
15 |  * @since 2021/03/23 19:10
16 |  */
17 | public class GeneratePkg extends BaseSubTask {
18 | 
19 |   public GeneratePkg() {
20 |     super("PKG installer");
21 |   }
22 | 
23 |   @Override
24 |   public boolean enabled() {
25 |     return packager.getMacConfig().isGeneratePkg();
26 |   }
27 | 
28 |   @Override
29 |   protected File run() throws Exception {
30 |     MacPackager macPackager = (MacPackager) packager;
31 | 
32 |     File appFile = macPackager.getAppFile();
33 |     String name = macPackager.getDisplayName();
34 |     File outputDirectory = macPackager.getOutputDir();
35 |     String version = macPackager.getVersion();
36 |     String outFileName =
37 |       String.format("%s_%s_%s.pkg", packager.getName(), version, StrUtil.nullToEmpty(packager.getArch()));
38 |     File pkgFile = new File(outputDirectory, outFileName);
39 | 
40 |     // invokes pkgbuild command
41 |     ExecUtils.create("pkgbuild")
42 |       .add("--install-location", "/Applications")
43 |       .add("--component", appFile)
44 |       .add(pkgFile)
45 |       .exec();
46 |     // checks if pkg file was created
47 |     if (!pkgFile.exists()) {
48 |       throw new Exception(name + " generation failed!");
49 |     }
50 | 
51 |     return pkgFile;
52 |   }
53 | 
54 | }
55 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/unclezs/novel/app/packager/subtask/windows/CreateExe.java:
--------------------------------------------------------------------------------
 1 | package com.unclezs.novel.app.packager.subtask.windows;
 2 | 
 3 | import cn.hutool.core.io.FileUtil;
 4 | import com.unclezs.novel.app.packager.subtask.BaseSubTask;
 5 | import com.unclezs.novel.app.packager.util.ExecUtils;
 6 | import com.unclezs.novel.app.packager.util.Logger;
 7 | import com.unclezs.novel.app.packager.util.VelocityUtils;
 8 | 
 9 | import java.io.File;
10 | 
11 | /**
12 |  * 使用exe4j创建exe文件
13 |  *
14 |  * @author blog.unclezs.com
15 |  * @since 2021/4/5 23:19
16 |  */
17 | public class CreateExe extends BaseSubTask {
18 | 
19 |   public CreateExe() {
20 |     super("创建EXE");
21 |   }
22 | 
23 |   @Override
24 |   protected Object run() throws Exception {
25 |     File config = new File(packager.getAssetsFolder(), packager.getName().concat(".exe4j"));
26 |     VelocityUtils.render("/packager/windows/exe4j.vm", config, packager);
27 |     File projectDir = packager.getProject().getProjectDir();
28 |     String exe4jc = FileUtil.file(projectDir.getParentFile(), "/packager/exe4j9/bin/exe4jc.exe").getAbsolutePath();
29 |     if (!FileUtil.exist(exe4jc)) {
30 |       exe4jc = "exe4jc";
31 |     }
32 |     Logger.info("使用 exe4jc 创建 exe 文件: {}", exe4jc);
33 |     ExecUtils.create(exe4jc).add(config).exec();
34 |     return new File(packager.getAppFolder(), packager.getDisplayName().concat(".exe"));
35 |   }
36 | }
37 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/icon/IconFont.java.vm:
--------------------------------------------------------------------------------
 1 | package $info.packageName;
 2 | 
 3 | import lombok.Getter;
 4 | 
 5 | /**
 6 |  * 字体图标 Generate By Gradle
 7 |  *
 8 |  * @author blog.unclezs.com
 9 |  * @date 2021/4/13 12:27
10 |  */
11 | @Getter
12 | public enum $info.className {
13 |     /**
14 |      * 字体图标
15 |      */
16 | #foreach($icon in $info.icons.entrySet())
17 |     $icon.key.toUpperCase()('$icon.value' )#if($foreach.count==$info.icons.size());#else,#end
18 | #end
19 | 
20 | private final char unicode;
21 | 
22 |     ${info.className}(char unicode){
23 |     this.unicode=unicode;
24 |     }
25 |     }


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/packager/linux/control.vm:
--------------------------------------------------------------------------------
 1 | Package: ${info.name}
 2 | Version: ${info.version}
 3 | Section: misc
 4 | Priority: optional
 5 | Architecture: amd64
 6 | Maintainer: ${info.organizationName} <$!{info.organizationEmail}>
 7 | Description: ${info.description}
 8 | Distribution: development
 9 | #if(${info.url})
10 | Homepage: ${info.url}
11 | #end


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/packager/linux/default-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/buildSrc/src/main/resources/packager/linux/default-icon.png


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/packager/linux/desktop.vm:
--------------------------------------------------------------------------------
 1 | [Desktop Entry]
 2 | Name=${info.displayName}
 3 | GenericName=${info.displayName}
 4 | Comment=${info.description}
 5 | Exec=/opt/${info.name}/${info.name} %U
 6 | Icon=/opt/${info.name}/${info.name}.png
 7 | Terminal=false
 8 | Type=Application
 9 | StartupNotify=true
10 | #if ($info.isThereFileAssociations())
11 | MimeType=${info.getMimeTypesListAsString(";")}
12 | #end
13 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/packager/mac/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/buildSrc/src/main/resources/packager/mac/background.png


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/packager/mac/default-icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/buildSrc/src/main/resources/packager/mac/default-icon.icns


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/packager/mac/startup.vm:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Mac OS X startup script generated by JavaPackager plugin
3 | SCRIPTPATH=`cd "$(dirname "$0")" ; pwd`
4 | osascript -e "do shell script quoted form of \"$SCRIPTPATH\" & \"/universalJavaApplicationStub $@\" with administrator privileges" &


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/packager/windows/default-icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/buildSrc/src/main/resources/packager/windows/default-icon.ico


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/packager/windows/msm.wxs.vm:
--------------------------------------------------------------------------------
 1 | #set ($name = $info.name.replaceAll("-", "_").replace(" ",""))
 2 | #set ($version = $info.version.replaceAll("-", "").replace("SNAPSHOT",""))
 3 | #set ($id = 0)
 4 | #macro(list $file)
 5 |     #set($guid = $GUID.randomUUID())
 6 |     #set($id = $id + 1)
 7 |     #if($file.isDirectory())
 8 |     <Directory Id="_${id}" Name="${file.name}">
 9 |         #foreach($child in $file.listFiles())
10 | 			#list($child)
11 | 		#end
12 |     </Directory>
13 |     #else
14 |     <Component Id="_${id}" Guid="${guid}" Win64="yes">
15 |         #if($file.equals(${info.executable}))
16 |           <File Id="exeFile" Name="${file.name}" KeyPath="yes" Source="${file}">
17 |             <Shortcut Id="ApplicationStartMenuShortcut" Name="${info.name}" Description="${info.description}" Directory="ProgramMenuFolder"/>
18 |           </File>
19 |           <RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"></RemoveFolder>
20 |           <RegistryValue Root="HKLM" Key="Software\\${info.organizationName}\\${info.name}" Name="installed" Type="integer" Value="1"/>
21 |         #else
22 |           <File Id="_${id}f" Name="${file.name}" KeyPath="yes" Source="${file}"/>
23 |         #end
24 |     </Component>
25 |     #end
26 | #end
27 | <?xml version="1.0" encoding="${charset}" standalone="yes"?>
28 | <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
29 |   <Module Id="app" Codepage="936" Language="2052" Version="${version}">
30 |     <Package Id="${GUID.randomUUID()}" Manufacturer="${info.organizationName}" InstallerVersion="200" Languages="2052" Platform="x64" SummaryCodepage="936" Description="${info.description}"/>
31 |     <Directory Id="TARGETDIR" Name="SourceDir">
32 |         #list(${info.appFolder})
33 |       <Directory Id="ProgramMenuFolder"/>
34 |     </Directory>
35 |   </Module>
36 | </Wix>
37 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/packager/windows/wxs.vm:
--------------------------------------------------------------------------------
 1 | #set($moduleName = $info.name.replaceAll("-", "_").replace(" ",""))
 2 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 3 | <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
 4 |   <Product Id="*" Language="2052" Codepage="936" Manufacturer="${info.organizationName}" Name="${info.name}" UpgradeCode="${info.winConfig.msiUpgradeCode}" Version="${info.winConfig.productVersion}">
 5 |     <Package Compressed="yes" InstallScope="perMachine" InstallerVersion="200" Languages="2052" Platform="x64" SummaryCodepage="936" Description="${info.description}"/>
 6 |     <Media Id="1" Cabinet="Application.cab" EmbedCab="yes"></Media>
 7 |     <Directory Id="TARGETDIR" Name="SourceDir">
 8 |       <Directory Id="ProgramFiles64Folder">
 9 |         <Merge Id="${moduleName}" Language="2052" SourceFile="${info.msmFile}" DiskId="1"/>
10 |       </Directory>
11 |     </Directory>
12 |     <Feature Id="_${GUID.randomUUID().toString().replaceAll('-','_')}" Absent="disallow" AllowAdvertise="no" ConfigurableDirectory="TARGETDIR" Description="${info.description}" Level="1"
13 |              Title="${info.displayName}">
14 |       <MergeRef Id="${moduleName}"/>
15 |     </Feature>
16 |     <Icon Id="ICONFILE" SourceFile="${info.winConfig.iconFile}"/>
17 |     <Property Id="ARPPRODUCTICON" Value="ICONFILE"/>
18 |       #if($info.licenseFile)
19 |         <WixVariable Id="WixUILicenseRtf" Value="LICENSE"/>
20 |       #end
21 |     <MajorUpgrade AllowDowngrades="no" AllowSameVersionUpgrades="no" DowngradeErrorMessage="A later version of ${info.name} is already installed. Setup will now exit." IgnoreRemoveFailure="no"/>
22 |   </Product>
23 | </Wix>
24 | 


--------------------------------------------------------------------------------
/buildSrc/src/main/resources/velocity_implicit.vm:
--------------------------------------------------------------------------------
1 | #* @implicitly included *#
2 | #* @vtlvariable name="info" type="com.unclezs.novel.app.icon.IconTask" file="icon/IconFont.java.vm" *#
3 | #* @vtlvariable name="StrUtil" type="cn.hutool.core.util.StrUtil" *#
4 | #* @vtlvariable name="info" type="com.unclezs.novel.app.packager.packager.AbstractPackager"*#
5 | 


--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
 1 | app.version=5.0.44
 2 | app.launcher.version=1.1.11-SNAPSHOT
 3 | org.gradle.jvmargs=-Dfile.encoding=UTF8
 4 | org.gradle.parallel=true
 5 | org.gradle.caching=false
 6 | org.gradle.logging.level=info
 7 | systemProp.file.encoding=UTF8
 8 | # all,fail,none,summary
 9 | org.gradle.warning.mode=none
10 | # https://github.com/gradle/gradle/issues/11308#issuecomment-554330650
11 | systemProp.org.gradle.internal.publish.checksums.insecure=true
12 | 


--------------------------------------------------------------------------------
/gradle/dependency-management.gradle:
--------------------------------------------------------------------------------
 1 | apply plugin: "io.spring.dependency-management"
 2 | 
 3 | Properties props = new Properties()
 4 | props.load(file("../gradle.properties").newDataInputStream())
 5 | 
 6 | dependencyManagement {
 7 |     dependencies {
 8 |         dependency "com.unclezs:jfx-launcher:${props.getProperty("app.launcher.version")}"
 9 |         dependency "com.unclezs:novel-analyzer:1.0.28"
10 | 
11 |         dependencySet("cn.hutool:5.8.0") {
12 |             entry "hutool-all"
13 |             entry "hutool-core"
14 |             entry "hutool-cache"
15 |             entry "hutool-system"
16 |         }
17 |         dependency "org.projectlombok:lombok:1.18.24"
18 |         dependency "com.github.tulskiy:jkeymaster:1.3"
19 |         // 不适配 JFX 17
20 |         // dependency "com.jfoenix:jfoenix:9.0.9"
21 |         dependency "org.rationalityfrontline.workaround:jfoenix:17.0.2"
22 |         dependency "org.slf4j:slf4j-api:1.7.30"
23 |         dependency "org.junit.jupiter:junit-jupiter-api:5.6.0"
24 |         dependency "com.google.code.gson:gson:2.8.6"
25 |         dependency "ch.qos.logback:logback-classic:1.2.3"
26 |         dependency 'org.apache.velocity:velocity-engine-core:2.3'
27 |         dependency 'com.netflix.nebula:gradle-ospackage-plugin:8.4.1'
28 |         dependency 'net.jsign:jsign-core:3.1'
29 |         dependency "com.j256.ormlite:ormlite-jdbc:5.3"
30 |         dependency "org.xerial:sqlite-jdbc:3.34.0"
31 |         dependency 'com.github.oshi:oshi-core:5.7.5'
32 |         dependency 'com.mixpanel:mixpanel-java:1.4.4'
33 |         dependency 'com.google.gradle:osdetector-gradle-plugin:1.7.0'
34 |         dependency 'org.openjdk.nashorn:nashorn-core:15.4'
35 |     }
36 |     generatedPomCustomization {
37 |         enabled = false
38 |     }
39 |     resolutionStrategy {
40 |         cacheChangingModulesFor 0, "seconds"
41 |     }
42 | }
43 | 
44 | configurations.all {
45 |     resolutionStrategy {
46 |         cacheChangingModulesFor 0, "seconds"
47 |         cacheDynamicVersionsFor 0, "seconds"
48 |     }
49 | }
50 | 


--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uncle-novel/uncle-novel/ce562f80e85ad93f7e6a4a942b26ccbc7c316a43/gradle/wrapper/gradle-wrapper.jar


--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | 


--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
 1 | rootProject.name = 'uncle-novel'
 2 | include 'app'
 3 | include 'app-framework'
 4 | include 'app-localized'
 5 | 
 6 | // 编译脚本为项目名.gradle
 7 | rootProject.children.each { project ->
 8 |     project.buildFileName = "${project.name}.gradle"
 9 | }
10 | 


--------------------------------------------------------------------------------