├── .gitattributes ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── .gitignore ├── .swiftlint.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Common └── Shared │ └── Extensions │ └── NSColor+.swift ├── LICENSE ├── Mac ├── AboutViewController.swift ├── AboutWindowController.swift ├── AppDelegate+URLRoutes.swift ├── AppDelegate.swift ├── Base.lproj │ ├── ContentViewController.xib │ └── Main.storyboard ├── Business │ ├── AppearanceType.swift │ ├── CodeBlock.swift │ ├── Commit.swift │ ├── IndexSetWrapper.swift │ ├── LanguageType.swift │ ├── Markdown.swift │ ├── Note.swift │ ├── NoteAttachment.swift │ ├── NoteAttribute.swift │ ├── NoteContainer.swift │ ├── NoteType.swift │ ├── Project.swift │ ├── RuntimeError.swift │ ├── Sidebar.swift │ ├── SidebarItem.swift │ ├── SidebarItemType.swift │ ├── SortBy.swift │ ├── SortDirection.swift │ ├── Storage.swift │ ├── TextBundleInfo.swift │ └── UndoData.swift ├── ContentViewController.swift ├── DetachedWindowController.swift ├── Extensions │ ├── CustomTextStorage+Images.swift │ ├── Date+.swift │ ├── DateFormatter+.swift │ ├── FileManager+.swift │ ├── ImageAttachment+.swift │ ├── NSAppearance+.swift │ ├── NSAttributedStringKey+.swift │ ├── NSColor+.swift │ ├── NSFont+.swift │ ├── NSImage+.swift │ ├── NSMutableAttributedString+.swift │ ├── NSMutableAttributedString+Attachments.swift │ ├── NSTextAttachment+.swift │ ├── NSTextField+.swift │ ├── NSTextStorage+.swift │ ├── NoteCellView+.swift │ ├── String+.swift │ ├── URL+.swift │ └── UTI.swift ├── Helpers │ ├── CodeTextProcessor.swift │ ├── CustomTextStorage.swift │ ├── Emoji.swift │ ├── FileSystemEventManager.swift │ ├── FileWatcher.swift │ ├── FileWatcherEvent.swift │ ├── ImagesProcessor.swift │ ├── KeychainConfiguration.swift │ ├── KeychainPasswordItem.swift │ ├── NameHelper.swift │ ├── NotesTextProcessor.swift │ ├── TextFormatter.swift │ ├── UserDataService.swift │ └── UserDefaultsManagement.swift ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ ├── Contents.json │ ├── code.colorset │ │ └── Contents.json │ ├── divider.colorset │ │ └── Contents.json │ ├── highlight.colorset │ │ └── Contents.json │ ├── home.png.imageset │ │ ├── Contents.json │ │ ├── home-white.png │ │ └── home.png │ ├── html.colorset │ │ └── Contents.json │ ├── link.colorset │ │ └── Contents.json │ ├── list.colorset │ │ └── Contents.json │ ├── mainBackground.colorset │ │ └── Contents.json │ ├── mainText.colorset │ │ └── Contents.json │ ├── makeNoteAsset.imageset │ │ ├── Contents.json │ │ └── blank.png │ ├── new.imageset │ │ ├── Contents.json │ │ ├── quill-pen-line-white.png │ │ └── quill-pen-line.png │ ├── new_rep.imageset │ │ ├── Contents.json │ │ ├── folder-add-line-white.png │ │ └── folder-add-line.png │ ├── pin.imageset │ │ ├── Contents.json │ │ ├── pin-white.png │ │ └── pin.png │ ├── repository.png.imageset │ │ ├── Contents.json │ │ ├── cell-white.png │ │ └── cell.png │ ├── reverseBackground.colorset │ │ └── Contents.json │ ├── title.colorset │ │ └── Contents.json │ ├── trash.png.imageset │ │ ├── Contents.json │ │ ├── garbage-white.png │ │ └── garbage.png │ ├── underlineColor.colorset │ │ └── Contents.json │ └── wand.and.rays.imageset │ │ ├── Contents.json │ │ └── wand_rays.png ├── Info.plist ├── MainWindow.swift ├── MainWindowController.swift ├── PreferencesGeneralViewController.swift ├── PrefsViewController.swift ├── PrefsWindowController.swift ├── ProjectSettingsViewController.swift ├── View │ ├── EditTextView.swift │ ├── EditorScrollView.swift │ ├── EditorSplitView.swift │ ├── EditorView.swift │ ├── MPreviewView.swift │ ├── MarkdownView.swift │ ├── NameTextField.swift │ ├── NoteCellView.swift │ ├── NoteRowView.swift │ ├── NotesTableView.swift │ ├── OutlineHeaderView.swift │ ├── PreviewTextField.swift │ ├── SearchTextField.swift │ ├── SharingService.swift │ ├── SidebarCellView.swift │ ├── SidebarNotesView.swift │ ├── SidebarProjectView.swift │ ├── SidebarSplitView.swift │ ├── SidebarTableRowView.swift │ ├── StorageView.swift │ ├── TitleBarView.swift │ ├── TitleTextField.swift │ └── Toast.swift ├── ViewController.swift ├── en.lproj │ ├── ContentViewController.strings │ ├── Info.plist │ ├── InfoPlist.strings │ └── Main.strings ├── ja.lproj │ ├── ContentViewController.strings │ ├── InfoPlist.strings │ └── Main.strings ├── zh-Hans.lproj │ ├── ContentViewController.strings │ ├── Info.plist │ └── InfoPlist.strings └── zh-Hant.lproj │ ├── ContentViewController.strings │ ├── InfoPlist.strings │ ├── Localizable.strings │ └── Main.strings ├── MiaoYan.entitlements ├── MiaoYan.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── MiaoYan.xcscheme ├── MiaoYan.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Podfile ├── README.md ├── README_EN.md ├── README_JP.md ├── Resources ├── DownView.bundle │ ├── css │ │ ├── heti.min.css │ │ └── katex.min.css │ ├── index.html │ ├── js │ │ ├── auto-render.min.js │ │ ├── d3.min.js │ │ ├── emoji.min.js │ │ ├── heti-addon.min.js │ │ ├── highlight.min.js │ │ ├── katex.min.js │ │ ├── markmap-view.min.js │ │ ├── markmap.min.js │ │ ├── mermaid.min.js │ │ └── plantuml-encoder.min.js │ ├── ppt.html │ └── ppt │ │ ├── dist │ │ ├── reset.css │ │ ├── reveal.css │ │ ├── reveal.js │ │ └── theme │ │ │ ├── common.css │ │ │ ├── night.css │ │ │ ├── night_var.css │ │ │ ├── white.css │ │ │ └── white_var.css │ │ └── plugin │ │ ├── highlight │ │ ├── highlight.js │ │ └── plugin.js │ │ ├── markdown │ │ ├── markdown.js │ │ └── plugin.js │ │ ├── math │ │ └── math.js │ │ ├── notes │ │ ├── notes.js │ │ ├── plugin.js │ │ └── speaker-view.html │ │ └── zoom │ │ ├── plugin.js │ │ └── zoom.js ├── Initial │ ├── Introduction to MiaoYan.md │ ├── MiaoYan PPT.md │ ├── 介绍妙言.md │ └── 妙言 PPT.md ├── Prettier.bundle │ ├── parser-markdown.js │ └── standalone.js ├── Prettier │ ├── Configuration │ │ ├── ArrowFunctionParenthesesStrategy.swift │ │ ├── ConfigurationKey.swift │ │ ├── EmbeddedLanguageFormattingStrategy.swift │ │ ├── EndOfLineStrategy.swift │ │ ├── HTMLWhitespaceSensitivityStrategy.swift │ │ ├── ProseWrapStrategy.swift │ │ ├── QuotePropertyStrategy.swift │ │ └── TrailingCommaStrategy.swift │ ├── FormatWithCursorResult.swift │ ├── Parser.swift │ ├── ParsingErrorDetails.swift │ ├── Plugin.swift │ ├── PrettierFormatter.swift │ └── PrettierMarkdown │ │ ├── MarkdownParser.swift │ │ └── MarkdownPlugin.swift └── package.json ├── en.lproj └── Localizable.strings ├── ja.lproj └── Localizable.strings └── zh-Hans.lproj ├── Localizable.strings └── Main.strings /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj merge=union 2 | Resources/DownView.bundle/** linguist-vendored 3 | Resources/Prettier.bundle/** linguist-vendored 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ['tw93'] 4 | custom: ['https://miaoyan.app/cats.html'] 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | [//]: # (🙊辛苦提 bug 前,去找一下历史的 issue 是否有提,同时检查一是否为最新版本。辛苦提供版本号、录屏或者截图、复现路径,期待解决的点这几个说明帮助我更好的解决问题。提交前请删除这些文字。) 11 | 12 | [//]: # (🙊Look for issue before you mention bugs Whether it is mentioned, and check whether it is the latest version. Need to provide the version number, screen capture or screenshot, reproduction path, and look forward to the points to be solved. These instructions help me better solve the problem. Please delete these words before submitting.) 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question or get support 4 | url: https://github.com/tw93/MiaoYan/discussions/categories/q-a 5 | about: Ask a question or request support for MiaoYan 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | [//]: # (🏂 很感谢你给妙言提建议,辛苦将你的需求描述清楚一些,特别期待你可以说明白这个功能很有用,没有加这个功能会让你很遗憾。提交前请删除这段文字.) 11 | 12 | [//]: # (🏂 Thank you for your advice to MiaoYan. Need you describe your needs clearly. I especially hope you can make it clear that you will regret not adding this ability. Please delete this text before submitting.) 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Pods 2 | *.xcuserstate 3 | xcuserdata 4 | Podfile.lock 5 | MiaoYan.xcworkspace/xcshareddata/* 6 | MiaoYan.xcworkspace/xcuserdata/* 7 | MiaoYan.xcodeproj/xcuserdata/* 8 | .idea/ 9 | Releases 10 | .DS_Store 11 | .vscode/ 12 | node_modules 13 | fonts/ 14 | Release 15 | appcast.xml 16 | Resources/DownView.bundle/demo.html 17 | docs/ 18 | index.html 19 | IrEditor 20 | package-lock.json 21 | .husky/ 22 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: # paths to include during linting. `--path` is ignored if present. 2 | - Common 3 | excluded: # paths to ignore during linting. Takes precedence over `included`. 4 | - Pods 5 | 6 | line_length: 7 | warning: 240 8 | ignores_function_declarations: true 9 | ignores_comments: true 10 | ignores_interpolated_strings: true 11 | ignores_urls: true 12 | 13 | disabled_rules: 14 | - force_cast 15 | - force_try 16 | - function_body_length 17 | - nesting 18 | - todo 19 | - type_body_length 20 | - type_name 21 | - identifier_name 22 | - opening_brace -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | tw93@qq.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to MiaoYan 2 | 3 | **Welcome to create [pull requests](https://github.com/tw93/MiaoYan/compare/) for bugfix, new component, doc, example, suggestion and anything.** 4 | 5 | ## Branch Management 6 | 7 | ```txt 8 | master 9 | ↑ 10 | dev <--- Develop/PR 11 | ``` 12 | 13 | - `dev` branch 14 | - `dev` is the developing branch. 15 | - It's **RECOMMENDED** to commit feature PR to `dev`. 16 | - `master` branch 17 | - `master` is the release branch,we will make tag and publish version on this branch. 18 | - If it is a document modification, it can be submitted to this branch. 19 | 20 | ## Commit Log 21 | 22 | please use 23 | 24 | ## More 25 | 26 | It is a good habit to create a feature request issue to discuss whether the feature is necessary before you implement it. However, it's unnecessary to create an issue to claim that you found a typo or improved the readability of documentation, just create a pull request. 27 | -------------------------------------------------------------------------------- /Common/Shared/Extensions/NSColor+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Tw93 on 2022/9/13. 3 | // Copyright (c) 2022 MiaoYan App. All rights reserved. 4 | // 5 | 6 | import AppKit 7 | 8 | extension NSColor { 9 | private static let cssColorNames: [String: String] = [ 10 | "black": "#000000", 11 | "silver": "#C0C0C0", 12 | "gray": "#808080", 13 | "white": "#FFFFFF", 14 | "maroon": "#800000", 15 | "red": "#FF0000", 16 | "purple": "#800080", 17 | "fuchsia": "#FF00FF", 18 | "green": "#008000", 19 | "lime": "#00FF00", 20 | "olive": "#808000", 21 | "yellow": "#FFFF00", 22 | "navy": "#000080", 23 | "blue": "#0000FF", 24 | "teal": "#008080", 25 | "aqua": "#00FFFF" 26 | ] 27 | 28 | convenience init?(css: String) { 29 | var colorString = css.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() 30 | if let hexValue = NSColor.cssColorNames[colorString] { 31 | colorString = hexValue 32 | } 33 | let r, g, b, a: CGFloat 34 | if colorString.hasPrefix("#") { 35 | let start = colorString.index(colorString.startIndex, offsetBy: 1) 36 | let hexColor = String(colorString[start...]) 37 | if hexColor.count == 6 { 38 | let scanner = Scanner(string: hexColor) 39 | var hexNumber: UInt64 = 0 40 | if scanner.scanHexInt64(&hexNumber) { 41 | r = CGFloat((hexNumber & 0xFF0000) >> 16) / 255 42 | g = CGFloat((hexNumber & 0x00FF00) >> 8) / 255 43 | b = CGFloat(hexNumber & 0x0000FF) / 255 44 | a = 1.0 45 | self.init(red: r, green: g, blue: b, alpha: a) 46 | return 47 | } 48 | } 49 | } 50 | return nil 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Tw93 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Mac/AboutViewController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class AboutViewController: NSViewController { 4 | override func viewDidLoad() { 5 | if let dictionary = Bundle.main.infoDictionary, 6 | let ver = dictionary["CFBundleShortVersionString"] as? String { 7 | versionLabel.stringValue = "Version \(ver)" 8 | versionLabel.isSelectable = true 9 | } 10 | } 11 | 12 | @IBOutlet var versionLabel: NSTextField! 13 | } 14 | -------------------------------------------------------------------------------- /Mac/AboutWindowController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class AboutWindowController: NSWindowController, NSWindowDelegate { 4 | override func windowDidLoad() { 5 | super.windowDidLoad() 6 | window?.delegate = self 7 | window?.title = "About" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Mac/Business/AppearanceType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum AppearanceType: Int { 4 | case System = 0x00 5 | case Light = 0x01 6 | case Dark = 0x02 7 | case Custom = 0x03 8 | } 9 | -------------------------------------------------------------------------------- /Mac/Business/CodeBlock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeBlock.swift 3 | // FSNotes 4 | // 5 | // Created by Олександр Глущенко on 8/28/19. 6 | // Copyright © 2019 Oleksandr Glushchenko. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | class CodeBlock: NSTextBlock { 12 | override func drawBackground(withFrame frameRect: NSRect, in controlView: NSView, characterRange charRange: NSRange, layoutManager: NSLayoutManager) { 13 | let selectionPath = NSBezierPath(roundedRect: frameRect, xRadius: 5, yRadius: 5) 14 | 15 | NotesTextProcessor.codeBackground.setFill() 16 | selectionPath.fill() 17 | } 18 | 19 | override func rectForLayout(at startingPoint: NSPoint, in rect: NSRect, textContainer: NSTextContainer, characterRange charRange: NSRange) -> NSRect { 20 | var res = super.rectForLayout(at: startingPoint, in: rect, textContainer: textContainer, characterRange: charRange) 21 | res.size.width = rect.size.width 22 | return res 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Mac/Business/Commit.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class Commit { 4 | private var date: String? 5 | private var hash: String 6 | 7 | init(hash: String) { 8 | self.hash = hash 9 | } 10 | 11 | public func setDate(date: String) { 12 | self.date = date 13 | } 14 | 15 | public func getDate() -> String? { 16 | date 17 | } 18 | 19 | public func getHash() -> String { 20 | hash 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Mac/Business/IndexSetWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IndexSetWrapper.swift 3 | // MiaoYan 4 | // 5 | // Created by Tw93 on 2023/5/5. 6 | // Copyright © 2023 MiaoYan App. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class IndexSetWrapper: NSObject, NSSecureCoding { 12 | static var supportsSecureCoding: Bool { 13 | return true 14 | } 15 | 16 | let indexSet: IndexSet 17 | 18 | init(indexSet: IndexSet) { 19 | self.indexSet = indexSet 20 | super.init() 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | guard let intArray = aDecoder.decodeObject(of: NSArray.self, forKey: "indexSet") as? [Int] else { 25 | return nil 26 | } 27 | self.indexSet = IndexSet(intArray) 28 | super.init() 29 | } 30 | 31 | func encode(with aCoder: NSCoder) { 32 | let intArray = indexSet.map { $0 } 33 | aCoder.encode(intArray, forKey: "indexSet") 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Mac/Business/LanguageType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum LanguageType: Int { 4 | case Chinese = 0x00 5 | case English = 0x01 6 | case Japanese = 0x02 7 | case ChineseTC = 0x03 8 | 9 | var description: String { 10 | switch self.rawValue { 11 | case 0x00: return "Chinese (Simplified)" 12 | case 0x01: return "English" 13 | case 0x02: return "Japanese" 14 | case 0x03: return "Chinese (Traditional)" 15 | default: return "Chinese (Simplified)" 16 | } 17 | } 18 | 19 | var code: String { 20 | switch self.rawValue { 21 | case 0x00: return "zh-Hans" 22 | case 0x01: return "en" 23 | case 0x02: return "ja" 24 | case 0x03: return "zh-Hant" 25 | default: return "zh-Hans" 26 | } 27 | } 28 | 29 | static func withName(rawValue: String) -> LanguageType { 30 | switch rawValue { 31 | case "English": return LanguageType.English 32 | case "Chinese (Simplified)": return LanguageType.Chinese 33 | case "Japanese": return LanguageType.Japanese 34 | case "Chinese (Traditional)": return LanguageType.ChineseTC 35 | default: return LanguageType.Chinese 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Mac/Business/Markdown.swift: -------------------------------------------------------------------------------- 1 | import libcmark_gfm 2 | 3 | func renderMarkdownHTML(markdown: String) -> String? { 4 | cmark_gfm_core_extensions_ensure_registered() 5 | 6 | guard let parser = cmark_parser_new(CMARK_OPT_FOOTNOTES) else { return nil } 7 | defer { cmark_parser_free(parser) } 8 | 9 | // 附加常见的 GFM 扩展 10 | let extensions = ["table", "autolink", "emoji", "footnotes", "strikethrough", "tasklist"] 11 | for extName in extensions { 12 | if let ext = cmark_find_syntax_extension(extName) { 13 | cmark_parser_attach_syntax_extension(parser, ext) 14 | } 15 | } 16 | 17 | cmark_parser_feed(parser, markdown, markdown.utf8.count) 18 | guard let node = cmark_parser_finish(parser) else { return nil } 19 | 20 | var res = String(cString: cmark_render_html(node, CMARK_OPT_UNSAFE | CMARK_OPT_HARDBREAKS, nil)) 21 | if UserDefaultsManagement.editorLineBreak == "Github" { 22 | res = String(cString: cmark_render_html(node, CMARK_OPT_UNSAFE | CMARK_OPT_NOBREAKS, nil)) 23 | } 24 | 25 | return res 26 | } 27 | -------------------------------------------------------------------------------- /Mac/Business/NoteAttachment.swift: -------------------------------------------------------------------------------- 1 | import AVKit 2 | import Foundation 3 | 4 | #if os(OSX) 5 | import Cocoa 6 | #else 7 | import UIKit 8 | #endif 9 | 10 | class NoteAttachment { 11 | public var title: String 12 | public var invalidateRange: NSRange? 13 | 14 | private var path: String 15 | public var url: URL 16 | private var cacheDir: URL? 17 | 18 | public var note: Note? 19 | public var shouldWriteCache = false 20 | public var imageCache: URL? 21 | 22 | init(title: String, path: String, url: URL, cache: URL?, invalidateRange: NSRange? = nil, note: Note? = nil) { 23 | self.title = title 24 | self.url = url 25 | self.path = path 26 | cacheDir = cache 27 | self.invalidateRange = invalidateRange 28 | self.note = note 29 | 30 | if url.isRemote() { 31 | let imageName = url.removingFragment().absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) 32 | 33 | if let directory = cache, 34 | let imageName = imageName { 35 | imageCache = directory.appendingPathComponent(imageName) 36 | } 37 | } 38 | } 39 | 40 | weak var weakTimer: Timer? 41 | 42 | public func getAttributedString(lazy: Bool = true) -> NSMutableAttributedString? { 43 | let imageKey = NSAttributedString.Key(rawValue: "com.tw93.miaoyan.image.url") 44 | let pathKey = NSAttributedString.Key(rawValue: "com.tw93.miaoyan.image.path") 45 | let titleKey = NSAttributedString.Key(rawValue: "com.tw93.miaoyan.image.title") 46 | 47 | if let dst = imageCache { 48 | if FileManager.default.fileExists(atPath: dst.path) { 49 | url = dst 50 | } else { 51 | shouldWriteCache = true 52 | } 53 | } 54 | 55 | guard FileManager.default.fileExists(atPath: url.path) else { return nil } 56 | guard let attachment = load(lazy: lazy) else { return nil } 57 | 58 | let attributedString = NSAttributedString(attachment: attachment) 59 | let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString) 60 | let paragraphStyle = NSTextStorage.getParagraphStyle() 61 | 62 | let attributes = [ 63 | titleKey: title, 64 | pathKey: path, 65 | imageKey: url, 66 | .link: url, 67 | .attachment: attachment, 68 | .paragraphStyle: paragraphStyle 69 | ] as [NSAttributedString.Key: Any] 70 | 71 | mutableAttributedString.addAttributes(attributes, range: NSRange(0 ..< 1)) 72 | 73 | return mutableAttributedString 74 | } 75 | 76 | public func cache(data: Data) { 77 | guard shouldWriteCache, let url = imageCache else { return } 78 | 79 | do { 80 | var isDirectory = ObjCBool(true) 81 | 82 | if let cacheDir = cacheDir, 83 | !FileManager.default.fileExists(atPath: cacheDir.path, isDirectory: &isDirectory) || !isDirectory.boolValue { 84 | try FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: false, attributes: nil) 85 | } 86 | 87 | try data.write(to: url, options: .atomic) 88 | } catch { 89 | print(error) 90 | } 91 | } 92 | 93 | public func getSize(url: URL) -> CGSize { 94 | var width = 0 95 | var height = 0 96 | var orientation = 0 97 | 98 | if url.isVideo { 99 | guard let track = AVURLAsset(url: url).tracks(withMediaType: AVMediaType.video).first else { 100 | return CGSize(width: width, height: height) 101 | } 102 | 103 | let size = track.naturalSize.applying(track.preferredTransform) 104 | return CGSize(width: abs(size.width), height: abs(size.height)) 105 | } 106 | 107 | let url = NSURL(fileURLWithPath: url.path) 108 | if let imageSource = CGImageSourceCreateWithURL(url, nil) { 109 | let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? 110 | width = imageProperties?[kCGImagePropertyPixelWidth] as? Int ?? 0 111 | height = imageProperties?[kCGImagePropertyPixelHeight] as? Int ?? 0 112 | orientation = imageProperties?[kCGImagePropertyOrientation] as? Int ?? 0 113 | 114 | if case 5 ... 8 = orientation { 115 | height = imageProperties?[kCGImagePropertyPixelWidth] as? Int ?? 0 116 | width = imageProperties?[kCGImagePropertyPixelHeight] as? Int ?? 0 117 | } 118 | } 119 | 120 | return CGSize(width: width, height: height) 121 | } 122 | 123 | public static func getCacheUrl(from url: URL, prefix: String = "Preview") -> URL? { 124 | var temporary = URL(fileURLWithPath: NSTemporaryDirectory()) 125 | temporary.appendPathComponent(prefix) 126 | 127 | if let filePath = url.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) { 128 | return temporary.appendingPathComponent(filePath) 129 | } 130 | 131 | return nil 132 | } 133 | 134 | public static func savePreviewImage(url: URL, image: Image, prefix: String = "Preview") { 135 | var temporary = URL(fileURLWithPath: NSTemporaryDirectory()) 136 | temporary.appendPathComponent(prefix) 137 | 138 | if !FileManager.default.fileExists(atPath: temporary.path) { 139 | try? FileManager.default.createDirectory(at: temporary, withIntermediateDirectories: true, attributes: nil) 140 | } 141 | 142 | if let url = getCacheUrl(from: url, prefix: prefix) { 143 | if let data = image.jpgData { 144 | try? data.write(to: url) 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Mac/Business/NoteAttribute.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum NoteAttribute { 4 | static let highlight = NSAttributedString.Key(rawValue: "com.tw93.search.highlight") 5 | 6 | static let all = Set([ 7 | highlight 8 | ]) 9 | } 10 | -------------------------------------------------------------------------------- /Mac/Business/NoteContainer.swift: -------------------------------------------------------------------------------- 1 | public enum NoteContainer: Int { 2 | case none = 0x01 3 | case textBundle = 0x02 4 | case textBundleV2 = 0x03 5 | 6 | static func withExt(rawValue: String) -> NoteContainer { 7 | switch rawValue { 8 | case "textbundle": return .textBundleV2 9 | default: return .none 10 | } 11 | } 12 | 13 | public var uti: String { 14 | switch self { 15 | case .textBundle: return "com.apple.package" 16 | case .textBundleV2: return "com.apple.package" 17 | case .none: return "" 18 | } 19 | } 20 | 21 | public var tag: Int { 22 | switch self { 23 | case .textBundle: return 0x02 24 | case .textBundleV2: return 0x03 25 | case .none: return 0x01 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Mac/Business/NoteType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum NoteType: String { 4 | case Markdown = "md" 5 | case RichText = "rtf" 6 | case PlainText = "txt" 7 | 8 | static func withExt(rawValue: String) -> NoteType { 9 | switch rawValue { 10 | case "markdown", "md", "mkd","txt": return NoteType.Markdown 11 | case "rtf": return NoteType.RichText 12 | default: return NoteType.PlainText 13 | } 14 | } 15 | 16 | static func withTag(rawValue: Int) -> NoteType { 17 | switch rawValue { 18 | case 1: return .Markdown 19 | case 2: return .RichText 20 | case 3: return .PlainText 21 | default: return .Markdown 22 | } 23 | } 24 | 25 | static func withUTI(rawValue: String) -> NoteType { 26 | switch rawValue { 27 | case "net.daringfireball.markdown": return .Markdown 28 | case "public.rtf": return .RichText 29 | case "public.plain-text": return .PlainText 30 | default: return .Markdown 31 | } 32 | } 33 | 34 | public var tag: Int { 35 | get { 36 | switch self { 37 | case .Markdown: return 1 38 | case .RichText: return 2 39 | case .PlainText: return 3 40 | } 41 | } 42 | } 43 | 44 | public var uti: String { 45 | get { 46 | switch self { 47 | case .Markdown: return "net.daringfireball.markdown" 48 | case .RichText: return "public.rtf" 49 | case .PlainText: return "public.plain-text" 50 | } 51 | } 52 | } 53 | 54 | public func getExtension(for container: NoteContainer) -> String { 55 | switch self { 56 | case .Markdown: 57 | if container == .textBundle || container == .none { 58 | return "md" 59 | } 60 | return "markdown" 61 | case .RichText: return "rtf" 62 | case .PlainText: return "txt" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Mac/Business/RuntimeError.swift: -------------------------------------------------------------------------------- 1 | struct RuntimeError: Error { 2 | let message: String 3 | 4 | init(_ message: String) { 5 | self.message = message 6 | } 7 | 8 | public var localizedDescription: String { 9 | message 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Mac/Business/Sidebar.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | typealias Image = NSImage 4 | 5 | class Sidebar { 6 | var list = [Any]() 7 | let storage = Storage.sharedInstance() 8 | public var items = [[SidebarItem]]() 9 | 10 | init() { 11 | let night = "" 12 | var system = [SidebarItem]() 13 | 14 | let notes = SidebarItem(name: NSLocalizedString("MiaoYan", comment: ""), type: .All, icon: getImage(named: "home\(night).png")) 15 | system.append(notes) 16 | 17 | if system.count > 0 { 18 | list = system 19 | } 20 | 21 | if UserDefaultsManagement.isSingleMode { 22 | return 23 | } 24 | 25 | let rootProjects = storage.getRootProjects() 26 | 27 | for project in rootProjects { 28 | let icon = getImage(named: "repository\(night).png") 29 | 30 | let childProjects = storage.getChildProjects(project: project) 31 | 32 | for childProject in childProjects { 33 | list.append(SidebarItem(name: childProject.label, project: childProject, type: .Category, icon: icon)) 34 | } 35 | } 36 | 37 | if storage.getAllTrash().count > 0 { 38 | let trashProject = Storage.sharedInstance().getDefaultTrash() 39 | let trash = SidebarItem(name: NSLocalizedString("Trash", comment: ""), project: trashProject, type: .Trash, icon: getImage(named: "trash\(night)")) 40 | list.append(trash) 41 | } 42 | } 43 | 44 | public func getList() -> [Any] { list } 45 | 46 | public func getProjects() -> [SidebarItem] { 47 | list.filter { ($0 as? SidebarItem)?.type == .Category && ($0 as? SidebarItem)?.project != nil && ($0 as? SidebarItem)!.project!.showInSidebar } as! [SidebarItem] 48 | } 49 | 50 | private func getImage(named: String) -> Image? { 51 | if let image = NSImage(named: named) { 52 | return image 53 | } 54 | 55 | return nil 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Mac/Business/SidebarItem.swift: -------------------------------------------------------------------------------- 1 | #if os(OSX) 2 | import Cocoa 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | class SidebarItem { 8 | var name: String 9 | var project: Project? 10 | var type: SidebarItemType 11 | public var icon: Image? 12 | 13 | init(name: String, project: Project? = nil, type: SidebarItemType, icon: Image? = nil) { 14 | self.name = name 15 | self.project = project 16 | self.type = type 17 | self.icon = icon 18 | } 19 | 20 | public func getName() -> String { name } 21 | 22 | public func isSelectable() -> Bool { true } 23 | 24 | public func isTrash() -> Bool { type == .Trash } 25 | 26 | public func isGroupItem() -> Bool { 27 | let notesLabel = NSLocalizedString("MiaoYan", comment: "Sidebar label") 28 | let trashLabel = NSLocalizedString("Trash", comment: "Sidebar label") 29 | if project == nil, [notesLabel, trashLabel].contains(name) { 30 | return true 31 | } 32 | return false 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Mac/Business/SidebarItemType.swift: -------------------------------------------------------------------------------- 1 | enum SidebarItemType: Int { 2 | case All = 0x01 3 | case Trash = 0x02 4 | case Category = 0x03 5 | } 6 | -------------------------------------------------------------------------------- /Mac/Business/SortBy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum SortBy: String { 4 | case none 5 | case modificationDate 6 | case creationDate 7 | case title 8 | } 9 | -------------------------------------------------------------------------------- /Mac/Business/SortDirection.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum SortDirection: String { 4 | case asc 5 | case desc 6 | } 7 | -------------------------------------------------------------------------------- /Mac/Business/TextBundleInfo.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct TextBundleInfo: Decodable { 4 | let version: Int 5 | let type: String 6 | let flatExtension: String? 7 | } 8 | -------------------------------------------------------------------------------- /Mac/Business/UndoData.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class UndoData: NSObject { 4 | let string: NSAttributedString 5 | let range: NSRange 6 | 7 | init(string: NSAttributedString, range: NSRange) { 8 | self.string = string 9 | self.range = range 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Mac/ContentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewController.swift 3 | // MiaoYan 4 | // 5 | // Created by Tw93 on 2022/7/6. 6 | // Copyright © 2022 MiaoYan App. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | class ContentViewController: NSViewController, NSPopoverDelegate { 12 | @IBOutlet var wordCount: NSTextField! 13 | @IBOutlet var updateTime: NSTextField! 14 | @IBOutlet var createTime: NSTextField! 15 | 16 | override func viewDidAppear() { 17 | guard let vc = ViewController.shared() else { return } 18 | let note = vc.notesTableView.getSelectedNote() 19 | var words = note?.getPrettifiedContent() 20 | 21 | words = vc.replace(validateString: words!, regex: "*+", content: "") 22 | words = vc.replace(validateString: words!, regex: "#+", content: "") 23 | words = vc.replace(validateString: words!, regex: "\\r\n", content: "") 24 | words = vc.replace(validateString: words!, regex: "\\n", content: "") 25 | words = vc.replace(validateString: words!, regex: "\\s", content: "") 26 | 27 | wordCount.stringValue = String(words!.count) 28 | updateTime.stringValue = note?.getUpdateTime() ?? "" 29 | createTime.stringValue = note?.getCreateTime() ?? "" 30 | super.viewDidAppear() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Mac/DetachedWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetachedWindowController.swift 3 | // MiaoYan 4 | // 5 | // Created by Tw93 on 2022/7/6. 6 | // Copyright © 2022 MiaoYan App. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | class DetachedWindowController: NSWindowController {} 12 | -------------------------------------------------------------------------------- /Mac/Extensions/CustomTextStorage+Images.swift: -------------------------------------------------------------------------------- 1 | import AVKit 2 | import Cocoa 3 | 4 | public extension NSTextStorage { 5 | func loadImage(attachment: NSTextAttachment, url: URL, range: NSRange) { 6 | EditTextView.imagesLoaderQueue.addOperation { 7 | guard url.isImage else { return } 8 | 9 | let size = attachment.bounds.size 10 | let retinaSize = CGSize(width: size.width * 2, height: size.height * 2) 11 | let image = NoteAttachment.getImage(url: url, size: retinaSize) 12 | 13 | DispatchQueue.main.async { 14 | let cell = NSTextAttachmentCell(imageCell: image) 15 | cell.image?.size = size 16 | attachment.image = nil 17 | attachment.attachmentCell = cell 18 | attachment.bounds = NSRect(x: 0, y: 0, width: size.width, height: size.height) 19 | 20 | if let manager = ViewController.shared()?.editArea.layoutManager { 21 | if #available(OSX 10.13, *) { 22 | } else { 23 | if self.mutableString.length >= range.upperBound { 24 | manager.invalidateLayout(forCharacterRange: range, actualCharacterRange: nil) 25 | } 26 | } 27 | manager.invalidateDisplay(forCharacterRange: range) 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Mac/Extensions/Date+.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Date { 4 | func toMillis() -> Int64! { 5 | Int64(timeIntervalSince1970 * 1000) 6 | } 7 | 8 | static func getCurrentFormattedDate() -> String { 9 | let dateFormatter = DateFormatter() 10 | dateFormatter.dateFormat = "yyyy-MM-dd HH.mm.ss.SSS" 11 | 12 | return dateFormatter.string(from: Date()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Mac/Extensions/DateFormatter+.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension DateFormatter { 4 | func formatDateForDisplay(_ date: Date) -> String { 5 | dateStyle = .short 6 | timeStyle = .none 7 | locale = NSLocale.autoupdatingCurrent 8 | return string(from: date) 9 | } 10 | 11 | func formatTimeForDisplay(_ date: Date) -> String { 12 | dateFormat = "yyyy/MM/dd HH:mm" 13 | return string(from: date) 14 | } 15 | 16 | func formatForDuplicate(_ date: Date) -> String { 17 | dateFormat = "HHmmssSS" 18 | return string(from: date) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Mac/Extensions/FileManager+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager+.swift 3 | // MiaoYan 4 | // 5 | // Created by Tw93 on 2022/7/5. 6 | // Copyright © 2022 MiaoYan App. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | extension FileManager { 12 | func directoryExists(atUrl url: URL) -> Bool { 13 | var isDirectory: ObjCBool = false 14 | let exists = self.fileExists(atPath: url.path, isDirectory: &isDirectory) 15 | return exists && isDirectory.boolValue 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Mac/Extensions/ImageAttachment+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageAttachment+.swift 3 | // FSNotes 4 | // 5 | // Created by Oleksandr Glushchenko on 1/19/19. 6 | // Copyright © 2019 Oleksandr Glushchenko. All rights reserved. 7 | // 8 | 9 | import AVKit 10 | import Cocoa 11 | 12 | extension NoteAttachment { 13 | func load(lazy: Bool = true) -> NSTextAttachment? { 14 | let attachment = NSTextAttachment() 15 | 16 | let imageSize = getSize(url: url) 17 | var size = getSize(width: imageSize.width, height: imageSize.height) 18 | 19 | if url.isImage { 20 | attachment.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height) 21 | attachment.image = NSImage(size: size) 22 | } else { 23 | size = NSSize(width: 40, height: 40) 24 | 25 | if let image = NSImage(named: "file") { 26 | let cell = NSTextAttachmentCell(imageCell: image) 27 | attachment.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height) 28 | attachment.attachmentCell = cell 29 | } 30 | } 31 | 32 | return attachment 33 | } 34 | 35 | private func getEditorView() -> EditTextView? { 36 | if let cvc = ViewController.shared(), let view = cvc.editArea { 37 | return view 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func getSize(width: CGFloat, height: CGFloat) -> NSSize { 44 | var maxWidth = UserDefaultsManagement.imagesWidth 45 | 46 | if maxWidth == Float(1000) { 47 | maxWidth = Float(width) 48 | } 49 | 50 | let ratio = Float(maxWidth) / Float(width) 51 | var size = NSSize(width: Int(width), height: Int(height)) 52 | 53 | if ratio < 1 { 54 | size = NSSize(width: Int(maxWidth), height: Int(Float(height) * Float(ratio))) 55 | } 56 | 57 | return size 58 | } 59 | 60 | static func getImageAndCacheData(url: URL, note: Note) -> Image? { 61 | var data: Data? 62 | 63 | let cacheDirectoryUrl = note.project.url.appendingPathComponent("/.cache/") 64 | 65 | if url.isRemote() || url.pathExtension.lowercased() == "png", let cacheName = url.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) { 66 | let imageCacheUrl = cacheDirectoryUrl.appendingPathComponent(cacheName) 67 | 68 | if !FileManager.default.fileExists(atPath: imageCacheUrl.path) { 69 | var isDirectory = ObjCBool(true) 70 | if !FileManager.default.fileExists(atPath: cacheDirectoryUrl.path, isDirectory: &isDirectory) || isDirectory.boolValue == false { 71 | do { 72 | try FileManager.default.createDirectory(at: imageCacheUrl.deletingLastPathComponent(), withIntermediateDirectories: false, attributes: nil) 73 | } catch { 74 | print(error) 75 | } 76 | } 77 | 78 | do { 79 | data = try Data(contentsOf: url) 80 | } catch { 81 | print(error) 82 | } 83 | 84 | } else { 85 | data = try? Data(contentsOf: imageCacheUrl) 86 | } 87 | } else { 88 | data = try? Data(contentsOf: url) 89 | } 90 | 91 | guard let imageData = data else { return nil } 92 | 93 | return Image(data: imageData) 94 | } 95 | 96 | static func getImage(url: URL, size: CGSize) -> NSImage? { 97 | let imageData = try? Data(contentsOf: url) 98 | var finalImage: NSImage? 99 | 100 | if url.isVideo { 101 | let asset = AVURLAsset(url: url, options: nil) 102 | let imgGenerator = AVAssetImageGenerator(asset: asset) 103 | if let cgImage = try? imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil) { 104 | finalImage = NSImage(cgImage: cgImage, size: size) 105 | } 106 | } else if let imageData = imageData { 107 | finalImage = NSImage(data: imageData) 108 | } 109 | 110 | guard let image = finalImage else { return nil } 111 | var thumbImage: NSImage? 112 | thumbImage = finalImage 113 | 114 | if let cacheURL = getCacheUrl(from: url, prefix: "ThumbnailsBig"), FileManager.default.fileExists(atPath: cacheURL.path) { 115 | thumbImage = NSImage(contentsOfFile: cacheURL.path) 116 | } else if let resizedImage = image.resized(to: size) { 117 | thumbImage = resizedImage 118 | savePreviewImage(url: url, image: resizedImage, prefix: "ThumbnailsBig") 119 | } 120 | 121 | return thumbImage 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Mac/Extensions/NSAppearance+.swift: -------------------------------------------------------------------------------- 1 | import AppKit.NSAppearance 2 | 3 | extension NSAppearance { 4 | var isDark: Bool { 5 | if name == .vibrantDark { return true } 6 | 7 | switch name { 8 | case .accessibilityHighContrastDarkAqua, 9 | .accessibilityHighContrastVibrantDark, 10 | .darkAqua: 11 | return true 12 | default: 13 | return false 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Mac/Extensions/NSAttributedStringKey+.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension NSAttributedString.Key { 4 | static var todo: NSAttributedString.Key { 5 | NSAttributedString.Key(rawValue: "com.tw93.miaoyan.image.todo") 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Mac/Extensions/NSColor+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSColor+.swift 3 | // MiaoYan 4 | // 5 | // Created by Tw93 on 2023/3/26. 6 | // Copyright © 2023 MiaoYan App. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | extension NSColor { 12 | private static let cssColorNames: [String: String] = [ 13 | "black": "#000000", 14 | "silver": "#C0C0C0", 15 | "gray": "#808080", 16 | "white": "#FFFFFF", 17 | "maroon": "#800000", 18 | "red": "#FF0000", 19 | "purple": "#800080", 20 | "fuchsia": "#FF00FF", 21 | "green": "#008000", 22 | "lime": "#00FF00", 23 | "olive": "#808000", 24 | "yellow": "#FFFF00", 25 | "navy": "#000080", 26 | "blue": "#0000FF", 27 | "teal": "#008080", 28 | "aqua": "#00FFFF" 29 | ] 30 | 31 | convenience init?(css: String) { 32 | var colorString = css.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() 33 | 34 | if let hexValue = NSColor.cssColorNames[colorString] { 35 | colorString = hexValue 36 | } 37 | 38 | let r, g, b, a: CGFloat 39 | 40 | if colorString.hasPrefix("#") { 41 | let start = colorString.index(colorString.startIndex, offsetBy: 1) 42 | let hexColor = String(colorString[start...]) 43 | 44 | if hexColor.count == 6 { 45 | let scanner = Scanner(string: hexColor) 46 | var hexNumber: UInt64 = 0 47 | 48 | if scanner.scanHexInt64(&hexNumber) { 49 | r = CGFloat((hexNumber & 0xFF0000) >> 16) / 255 50 | g = CGFloat((hexNumber & 0x00FF00) >> 8) / 255 51 | b = CGFloat(hexNumber & 0x0000FF) / 255 52 | a = 1.0 53 | 54 | self.init(red: r, green: g, blue: b, alpha: a) 55 | return 56 | } 57 | } 58 | } 59 | 60 | return nil 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Mac/Extensions/NSFont+.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | extension NSFont { 4 | var isBold: Bool { fontDescriptor.symbolicTraits.contains(.bold) } 5 | 6 | var isItalic: Bool { fontDescriptor.symbolicTraits.contains(.italic) } 7 | 8 | var height: CGFloat { 9 | let constraintRect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) 10 | 11 | let boundingBox = "A".boundingRect(with: constraintRect, options: NSString.DrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: self], context: nil) 12 | 13 | return boundingBox.height 14 | } 15 | 16 | var lineHeight: CGFloat { 17 | CGFloat(ceilf(Float(ascender + abs(descender) + leading))) 18 | } 19 | 20 | var lineHeightCustom: CGFloat { 21 | CGFloat(ceilf(Float(ascender + abs(descender) + leading))) 22 | } 23 | 24 | static func italicFont() -> NSFont { 25 | NSFontManager().convert(UserDefaultsManagement.noteFont, toHaveTrait: .italicFontMask) 26 | } 27 | 28 | static func boldFont() -> NSFont { 29 | NSFontManager().convert(UserDefaultsManagement.noteFont, toHaveTrait: .boldFontMask) 30 | } 31 | 32 | func bold() -> NSFont { 33 | guard let family = UserDefaultsManagement.noteFont.familyName else { 34 | return UserDefaultsManagement.noteFont 35 | } 36 | 37 | var mask = 0 38 | if isItalic { 39 | mask = NSFontBoldTrait | NSFontItalicTrait 40 | } else { 41 | mask = NSFontBoldTrait 42 | } 43 | 44 | if let font = NSFontManager().font(withFamily: family, traits: NSFontTraitMask(rawValue: NSFontTraitMask.RawValue(mask)), weight: 5, size: CGFloat(UserDefaultsManagement.fontSize)) { 45 | return font 46 | } 47 | 48 | return UserDefaultsManagement.noteFont 49 | } 50 | 51 | func titleBold() -> NSFont { 52 | guard let family = UserDefaultsManagement.titleFont.familyName else { 53 | return UserDefaultsManagement.titleFont 54 | } 55 | 56 | var mask = 0 57 | if isItalic { 58 | mask = NSFontBoldTrait | NSFontItalicTrait 59 | } else { 60 | mask = NSFontBoldTrait 61 | } 62 | 63 | if let font = NSFontManager().font(withFamily: family, traits: NSFontTraitMask(rawValue: NSFontTraitMask.RawValue(mask)), weight: 5, size: CGFloat(UserDefaultsManagement.titleFontSize)) { 64 | return font 65 | } 66 | 67 | return UserDefaultsManagement.titleFont 68 | } 69 | 70 | func unBold() -> NSFont { 71 | guard let family = UserDefaultsManagement.noteFont.familyName else { 72 | return UserDefaultsManagement.noteFont 73 | } 74 | 75 | var mask = 0 76 | if isItalic { 77 | mask = NSFontItalicTrait 78 | } 79 | 80 | if let font = NSFontManager().font(withFamily: family, traits: NSFontTraitMask(rawValue: NSFontTraitMask.RawValue(mask)), weight: 5, size: CGFloat(UserDefaultsManagement.fontSize)) { 81 | return font 82 | } 83 | 84 | return UserDefaultsManagement.noteFont 85 | } 86 | 87 | func italic() -> NSFont { 88 | guard let family = UserDefaultsManagement.noteFont.familyName else { 89 | return UserDefaultsManagement.noteFont 90 | } 91 | 92 | var mask = 0 93 | if isBold { 94 | mask = NSFontBoldTrait | NSFontItalicTrait 95 | } else { 96 | mask = NSFontItalicTrait 97 | } 98 | 99 | if let font = NSFontManager().font(withFamily: family, traits: NSFontTraitMask(rawValue: NSFontTraitMask.RawValue(mask)), weight: 5, size: CGFloat(UserDefaultsManagement.fontSize)) { 100 | return font 101 | } 102 | 103 | return UserDefaultsManagement.noteFont 104 | } 105 | 106 | func unItalic() -> NSFont { 107 | guard let family = UserDefaultsManagement.noteFont.familyName else { 108 | return UserDefaultsManagement.noteFont 109 | } 110 | 111 | var mask = 0 112 | if isBold { 113 | mask = NSFontBoldTrait 114 | } 115 | 116 | if let font = NSFontManager().font(withFamily: family, traits: NSFontTraitMask(rawValue: NSFontTraitMask.RawValue(mask)), weight: 5, size: CGFloat(UserDefaultsManagement.fontSize)) { 117 | return font 118 | } 119 | 120 | return UserDefaultsManagement.noteFont 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Mac/Extensions/NSMutableAttributedString+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString+.swift 3 | // FSNotes 4 | // 5 | // Created by Oleksandr Glushchenko on 7/21/18. 6 | // Copyright © 2018 Oleksandr Glushchenko. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) 12 | import UIKit 13 | #else 14 | import Cocoa 15 | #endif 16 | 17 | public extension NSMutableAttributedString { 18 | func unLoadImages(note: Note? = nil) -> NSMutableAttributedString { 19 | var offset = 0 20 | let content = mutableCopy() as? NSMutableAttributedString 21 | 22 | enumerateAttribute(.attachment, in: NSRange(location: 0, length: length)) { value, range, _ in 23 | 24 | if let textAttachment = value as? NSTextAttachment, 25 | self.attribute(.todo, at: range.location, effectiveRange: nil) == nil { 26 | var path: String? 27 | var title: String? 28 | 29 | let filePathKey = NSAttributedString.Key(rawValue: "com.tw93.miaoyan.image.path") 30 | let titleKey = NSAttributedString.Key(rawValue: "com.tw93.miaoyan.image.title") 31 | 32 | if let filePath = self.attribute(filePathKey, at: range.location, effectiveRange: nil) as? String { 33 | path = filePath.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) 34 | title = self.attribute(titleKey, at: range.location, effectiveRange: nil) as? String 35 | } else if let note = note, 36 | let imageData = textAttachment.fileWrapper?.regularFileContents { 37 | path = ImagesProcessor.writeFile(data: imageData, note: note) 38 | } else if let note = note, 39 | let imageData = textAttachment.contents { 40 | path = ImagesProcessor.writeFile(data: imageData, note: note) 41 | } 42 | 43 | let newRange = NSRange(location: range.location + offset, length: range.length) 44 | 45 | guard let unwrappedPath = path, unwrappedPath.count > 0 else { return } 46 | 47 | let unrappedTitle = title ?? "" 48 | 49 | content?.removeAttribute(.attachment, range: newRange) 50 | content?.replaceCharacters(in: newRange, with: "![\(unrappedTitle)](\(unwrappedPath))") 51 | offset += 4 + unwrappedPath.count + unrappedTitle.count 52 | } 53 | } 54 | 55 | return content! 56 | } 57 | 58 | func unLoadCheckboxes() -> NSMutableAttributedString { 59 | var offset = 0 60 | let content = mutableCopy() as? NSMutableAttributedString 61 | 62 | enumerateAttribute(.attachment, in: NSRange(location: 0, length: length)) { value, range, _ in 63 | if value != nil { 64 | let newRange = NSRange(location: range.location + offset, length: 1) 65 | 66 | guard range.length == 1, 67 | let value = self.attribute(.todo, at: range.location, effectiveRange: nil) as? Int 68 | else { return } 69 | 70 | var gfm = "- [ ]" 71 | if value == 1 { 72 | gfm = "- [x]" 73 | } 74 | content?.replaceCharacters(in: newRange, with: gfm) 75 | offset += 4 76 | } 77 | } 78 | 79 | return content! 80 | } 81 | 82 | func unLoad() -> NSMutableAttributedString { 83 | unLoadCheckboxes().unLoadImages() 84 | } 85 | 86 | #if os(OSX) 87 | func unLoadUnderlines() -> NSMutableAttributedString { 88 | enumerateAttribute(.underlineStyle, in: NSRange(location: 0, length: length)) { value, range, _ in 89 | if value != nil { 90 | addAttribute(.underlineColor, value: NSColor.black, range: range) 91 | } 92 | } 93 | 94 | return self 95 | } 96 | #endif 97 | 98 | func loadUnderlines() { 99 | enumerateAttribute(.underlineStyle, in: NSRange(location: 0, length: length)) { value, range, _ in 100 | if value != nil { 101 | addAttribute(.underlineColor, value: NotesTextProcessor.underlineColor, range: range) 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Mac/Extensions/NSMutableAttributedString+Attachments.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString+Attachments.swift 3 | // FSNotes 4 | // 5 | // Created by Олександр Глущенко on 10/3/19. 6 | // Copyright © 2019 Oleksandr Glushchenko. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSMutableAttributedString { 12 | func loadImages(note: Note) { 13 | let paragraphRange = NSRange(0 ..< length) 14 | var offset = 0 15 | 16 | NotesTextProcessor.imageInlineRegex.matches(string, range: paragraphRange) { result in 17 | guard var range = result?.range else { return } 18 | 19 | range = NSRange(location: range.location - offset, length: range.length) 20 | let mdLink = self.attributedSubstring(from: range).string 21 | 22 | var path = String() 23 | var title = String() 24 | 25 | if let titleRange = result?.range(at: 2) { 26 | title = self.mutableString.substring(with: NSRange(location: titleRange.location - offset, length: titleRange.length)) 27 | } 28 | 29 | if let linkRange = result?.range(at: 3) { 30 | path = self.mutableString.substring(with: NSRange(location: linkRange.location - offset, length: linkRange.length)) 31 | } 32 | 33 | guard let cleanPath = path.removingPercentEncoding, let imageURL = note.getImageUrl(imageName: cleanPath) else { return } 34 | 35 | let cacheUrl = note.project.url.appendingPathComponent("/.cache/") 36 | let imageAttachment = NoteAttachment(title: title, path: cleanPath, url: imageURL, cache: cacheUrl, note: note) 37 | 38 | if let attributedStringWithImage = imageAttachment.getAttributedString() { 39 | offset += mdLink.count - 1 40 | self.replaceCharacters(in: range, with: attributedStringWithImage) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Mac/Extensions/NSTextAttachment+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTextAttachment+.swift 3 | // FSNotes 4 | // 5 | // Created by Олександр Глущенко on 10/2/19. 6 | // Copyright © 2019 Oleksandr Glushchenko. All rights reserved. 7 | // 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | #else 12 | import UIKit 13 | #endif 14 | 15 | extension NSTextAttachment { 16 | func isFile() -> Bool { 17 | #if os(iOS) 18 | return false 19 | #endif 20 | 21 | #if os(OSX) 22 | return (attachmentCell?.cellSize().height == 40) 23 | #endif 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Mac/Extensions/NSTextField+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Tw93 on 2022/9/22. 3 | // Copyright (c) 2022 MiaoYan App. All rights reserved. 4 | // 5 | 6 | import Cocoa 7 | 8 | extension NSTextField { 9 | func addCharacterSpacing(isTitle: Bool = false) { 10 | if let string = attributedStringValue.mutableCopy() as? NSMutableAttributedString { 11 | let labelText = stringValue 12 | let range = NSMakeRange(0, labelText.count-1) 13 | string.addAttribute(.kern, value: UserDefaultsManagement.windowLetterSpacing, range: range) 14 | string.fixAttributes(in: range) 15 | attributedStringValue = string 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Mac/Extensions/NSTextStorage+.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AppKit 3 | 4 | public extension NSTextStorage { 5 | func updateFont() { 6 | beginEditing() 7 | enumerateAttribute(.font, in: NSRange(location: 0, length: length)) { value, range, _ in 8 | if let font = value as? NSFont, let familyName = UserDefaultsManagement.noteFont.familyName { 9 | let newFontDescriptor = font.fontDescriptor 10 | .withFamily(familyName) 11 | .withSymbolicTraits(font.fontDescriptor.symbolicTraits) 12 | 13 | if let newFont = NSFont(descriptor: newFontDescriptor, size: CGFloat(UserDefaultsManagement.fontSize)) { 14 | removeAttribute(.font, range: range) 15 | addAttribute(.font, value: newFont, range: range) 16 | fixAttributes(in: range) 17 | } 18 | } 19 | } 20 | endEditing() 21 | } 22 | 23 | static func getParagraphStyle() -> NSMutableParagraphStyle { 24 | let paragraphStyle = NSMutableParagraphStyle() 25 | let fontSize = UserDefaultsManagement.fontSize 26 | 27 | // 优先使用默认的 28 | let editorLineHeight = UserDefaultsManagement.editorLineHeight 29 | let editorLineSpacing = UserDefaultsManagement.editorLineSpacing 30 | let lineHeight = CGFloat(editorLineHeight * CGFloat(fontSize)) + editorLineSpacing 31 | 32 | paragraphStyle.alignment = .left 33 | paragraphStyle.lineSpacing = editorLineSpacing 34 | paragraphStyle.lineHeightMultiple = editorLineHeight 35 | paragraphStyle.maximumLineHeight = lineHeight 36 | paragraphStyle.minimumLineHeight = lineHeight 37 | 38 | return paragraphStyle 39 | } 40 | 41 | func updateParagraphStyle() { 42 | beginEditing() 43 | let attachmentParagraph = NSTextStorage.getParagraphStyle() 44 | mutableString.enumerateSubstrings(in: NSRange(0.. Data { 20 | try self.withUnsafeFileSystemRepresentation { fileSystemPath -> Data in 21 | 22 | // Determine attribute size: 23 | let length = getxattr(fileSystemPath, name, nil, 0, 0, 0) 24 | guard length >= 0 else { throw URL.posixError(errno) } 25 | 26 | // Create buffer with required size: 27 | var data = Data(count: length) 28 | let count = data.count 29 | 30 | // Retrieve attribute: 31 | let result = data.withUnsafeMutableBytes { 32 | getxattr(fileSystemPath, name, $0.baseAddress, count, 0, 0) 33 | } 34 | guard result >= 0 else { throw URL.posixError(errno) } 35 | return data 36 | } 37 | } 38 | 39 | /// Set extended attribute. 40 | func setExtendedAttribute(data: Data, forName name: String) throws { 41 | try self.withUnsafeFileSystemRepresentation { fileSystemPath in 42 | let result = data.withUnsafeBytes { 43 | setxattr(fileSystemPath, name, $0.baseAddress, data.count, 0, 0) 44 | } 45 | guard result == 0 else { throw URL.posixError(errno) } 46 | } 47 | } 48 | 49 | /// Remove extended attribute. 50 | func removeExtendedAttribute(forName name: String) throws { 51 | try self.withUnsafeFileSystemRepresentation { fileSystemPath in 52 | let result = removexattr(fileSystemPath, name, 0) 53 | guard result == 0 else { throw URL.posixError(errno) } 54 | } 55 | } 56 | 57 | /// Get list of all extended attributes. 58 | func listExtendedAttributes() throws -> [String] { 59 | let list = try self.withUnsafeFileSystemRepresentation { fileSystemPath -> [String] in 60 | let length = listxattr(fileSystemPath, nil, 0, 0) 61 | guard length >= 0 else { throw URL.posixError(errno) } 62 | 63 | // Create buffer with required size: 64 | var namebuf = [CChar](repeating: 0, count: length) 65 | 66 | // Retrieve attribute list: 67 | let result = listxattr(fileSystemPath, &namebuf, namebuf.count, 0) 68 | guard result >= 0 else { throw URL.posixError(errno) } 69 | 70 | // Extract attribute names: 71 | let list = namebuf.split(separator: 0).compactMap { 72 | $0.withUnsafeBufferPointer { 73 | $0.withMemoryRebound(to: UInt8.self) { 74 | String(bytes: $0, encoding: .utf8) 75 | } 76 | } 77 | } 78 | return list 79 | } 80 | return list 81 | } 82 | 83 | /// Helper function to create an NSError from a Unix errno. 84 | private static func posixError(_ err: Int32) -> NSError { 85 | NSError(domain: NSPOSIXErrorDomain, code: Int(err), 86 | userInfo: [NSLocalizedDescriptionKey: String(cString: strerror(err))]) 87 | } 88 | 89 | // Access the URL parameters eg nv://make?title=blah&txt=body like so: 90 | // let titleStr = myURL['title'] 91 | subscript(queryParam: String) -> String? { 92 | guard let url = URLComponents(string: self.absoluteString) else { return nil } 93 | return url.queryItems?.first(where: { $0.name == queryParam })?.value 94 | } 95 | 96 | func isRemote() -> Bool { 97 | self.absoluteString.starts(with: "http://") || self.absoluteString.starts(with: "https://") 98 | } 99 | 100 | var attributes: [FileAttributeKey: Any]? { 101 | do { 102 | return try FileManager.default.attributesOfItem(atPath: path) 103 | } catch let error as NSError { 104 | print("FileAttribute error: \(error)") 105 | } 106 | return nil 107 | } 108 | 109 | var fileSize: UInt64 { 110 | self.attributes?[.size] as? UInt64 ?? UInt64(0) 111 | } 112 | 113 | func removingFragment() -> URL { 114 | var string = self.absoluteString 115 | if let query = query { 116 | string = string.replacingOccurrences(of: "?\(query)", with: "") 117 | } 118 | 119 | if let fragment = fragment { 120 | string = string.replacingOccurrences(of: "#\(fragment)", with: "") 121 | } 122 | 123 | return URL(string: string) ?? self 124 | } 125 | 126 | var typeIdentifier: String? { 127 | (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier 128 | } 129 | 130 | var fileUTType: CFString? { 131 | let unmanagedFileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil) 132 | return unmanagedFileUTI?.takeRetainedValue() 133 | } 134 | 135 | var isVideo: Bool { 136 | guard let fileUTI = fileUTType else { return false } 137 | 138 | return UTTypeConformsTo(fileUTI, kUTTypeMovie) 139 | || UTTypeConformsTo(fileUTI, kUTTypeVideo) 140 | || UTTypeConformsTo(fileUTI, kUTTypeQuickTimeMovie) 141 | || UTTypeConformsTo(fileUTI, kUTTypeMPEG) 142 | || UTTypeConformsTo(fileUTI, kUTTypeMPEG2Video) 143 | || UTTypeConformsTo(fileUTI, kUTTypeMPEG2TransportStream) 144 | || UTTypeConformsTo(fileUTI, kUTTypeMPEG4) 145 | || UTTypeConformsTo(fileUTI, kUTTypeAppleProtectedMPEG4Video) 146 | || UTTypeConformsTo(fileUTI, kUTTypeAVIMovie) 147 | } 148 | 149 | var isImage: Bool { 150 | guard let fileUTI = fileUTType else { return false } 151 | 152 | return UTTypeConformsTo(fileUTI, kUTTypeImage) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Mac/Extensions/UTI.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(OSX) 3 | import CoreServices 4 | #elseif os(iOS) 5 | import MobileCoreServices 6 | #endif 7 | 8 | public extension String { 9 | func tag(withClass: CFString) -> String? { 10 | UTTypeCopyPreferredTagWithClass(self as CFString, withClass)?.takeRetainedValue() as String? 11 | } 12 | 13 | func uti(withClass: CFString) -> String? { 14 | UTTypeCreatePreferredIdentifierForTag(withClass, self as CFString, nil)?.takeRetainedValue() as String? 15 | } 16 | 17 | var utiMimeType: String? { 18 | tag(withClass: kUTTagClassMIMEType) 19 | } 20 | 21 | var utiFileExtension: String? { 22 | tag(withClass: kUTTagClassFilenameExtension) 23 | } 24 | 25 | var mimeTypeUTI: String? { 26 | uti(withClass: kUTTagClassMIMEType) 27 | } 28 | 29 | var fileExtensionUTI: String? { 30 | uti(withClass: kUTTagClassFilenameExtension) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Mac/Helpers/Emoji.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Emoji.swift 3 | // MiaoYan 4 | // 5 | // Created by Tw93 on 2022/7/28. 6 | // Copyright © 2022 MiaoYan App. All rights reserved. 7 | // 8 | 9 | public let EmojiPattern = "[©®‼⁉™ℹ↔↕↖↗↘↙↩↪⌚⌛⌨⏏⏩⏪⏫⏬⏭⏮⏯⏰⏱⏲⏳⏸⏹⏺0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣Ⓜ▪▫▶◀◻◼◽◾☀☁☂☃☄☎☑☔☕☘☝☠☢☣☦☪☮☯☸☹☺♀♂♈♉♊♋♌♍♎♏♐♑♒♓♠♣♥♦♨♻♿⚒⚓⚔⚕⚖⚗⚙⚛⚜⚠⚡⚪⚫⚰⚱⚽⚾⛄⛅⛈⛎⛏⛑⛓⛔⛩⛪⛰⛱⛲⛳⛴⛵⛷⛸⛹⛺⛽✂✅✈✉✊✋✌✍✏✒✔✖✝✡✨✳✴❄❇❌❎❓❔❕❗❣❤➕➖➗➡➰➿⤴⤵⬅⬆⬇⬛⬜⭐⭕〰〽㊗㊙🀄🃏🅰🅱🅾🅿🆎🆑🆒🆓🆔🆕🆖🆗🆘🆙🆚🇦🇧🇨🇩🇪🇫🇬🇭🇮🇯🇰🇱🇲🇳🇴🇵🇶🇷🇸🇹🇺🇻🇼🇽🇾🇿🈁🈂🈚🈯🈲🈳🈴🈵🈶🈷🈸🈹🈺🉐🉑🌀🌁🌂🌃🌄🌅🌆🌇🌈🌉🌊🌋🌌🌍🌎🌏🌐🌑🌒🌓🌔🌕🌖🌗🌘🌙🌚🌛🌜🌝🌞🌟🌠🌡🌤🌥🌦🌧🌨🌩🌪🌫🌬🌭🌮🌯🌰🌱🌲🌳🌴🌵🌶🌷🌸🌹🌺🌻🌼🌽🌾🌿🍀🍁🍂🍃🍄🍅🍆🍇🍈🍉🍊🍋🍌🍍🍎🍏🍐🍑🍒🍓🍔🍕🍖🍗🍘🍙🍚🍛🍜🍝🍞🍟🍠🍡🍢🍣🍤🍥🍦🍧🍨🍩🍪🍫🍬🍭🍮🍯🍰🍱🍲🍳🍴🍵🍶🍷🍸🍹🍺🍻🍼🍽🍾🍿🎀🎁🎂🎃🎄🎅🎆🎇🎈🎉🎊🎋🎌🎍🎎🎏🎐🎑🎒🎓🎖🎗🎙🎚🎛🎞🎟🎠🎡🎢🎣🎤🎥🎦🎧🎨🎩🎪🎫🎬🎭🎮🎯🎰🎱🎲🎳🎴🎵🎶🎷🎸🎹🎺🎻🎼🎽🎾🎿🏀🏁🏂🏃🏄🏅🏆🏇🏈🏉🏊🏋🏌🏍🏎🏏🏐🏑🏒🏓🏔🏕🏖🏗🏘🏙🏚🏛🏜🏝🏞🏟🏠🏡🏢🏣🏤🏥🏦🏧🏨🏩🏪🏫🏬🏭🏮🏯🏰🏳🏴🏵🏷🏸🏹🏺🏻🏼🏽🏾🏿🐀🐁🐂🐃🐄🐅🐆🐇🐈🐉🐊🐋🐌🐍🐎🐏🐐🐑🐒🐓🐔🐕🐖🐗🐘🐙🐚🐛🐜🐝🐞🐟🐠🐡🐢🐣🐤🐥🐦🐧🐨🐩🐪🐫🐬🐭🐮🐯🐰🐱🐲🐳🐴🐵🐶🐷🐸🐹🐺🐻🐼🐽🐾🐿👀👁👂👃👄👅👆👇👈👉👊👋👌👍👎👏👐👑👒👓👔👕👖👗👘👙👚👛👜👝👞👟👠👡👢👣👤👥👦👧👨👩👪👫👬👭👮👯👰👱👲👳👴👵👶👷👸👹👺👻👼👽👾👿💀💁💂💃💄💅💆💇💈💉💊💋💌💍💎💏💐💑💒💓💔💕💖💗💘💙💚💛💜💝💞💟💠💡💢💣💤💥💦💧💨💩💪💫💬💭💮💯💰💱💲💳💴💵💶💷💸💹💺💻💼💽💾💿📀📁📂📃📄📅📆📇📈📉📊📋📌📍📎📏📐📑📒📓📔📕📖📗📘📙📚📛📜📝📞📟📠📡📢📣📤📥📦📧📨📩📪📫📬📭📮📯📰📱📲📳📴📵📶📷📸📹📺📻📼📽📿🔀🔁🔂🔃🔄🔅🔆🔇🔈🔉🔊🔋🔌🔍🔎🔏🔐🔑🔒🔓🔔🔕🔖🔗🔘🔙🔚🔛🔜🔝🔞🔟🔠🔡🔢🔣🔤🔥🔦🔧🔨🔩🔪🔫🔬🔭🔮🔯🔰🔱🔲🔳🔴🔵🔶🔷🔸🔹🔺🔻🔼🔽🕉🕊🕋🕌🕍🕎🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚🕛🕜🕝🕞🕟🕠🕡🕢🕣🕤🕥🕦🕧🕯🕰🕳🕴🕵🕶🕷🕸🕹🕺🖇🖊🖋🖌🖍🖐🖕🖖🖤🖥🖨🖱🖲🖼🗂🗃🗄🗑🗒🗓🗜🗝🗞🗡🗣🗨🗯🗳🗺🗻🗼🗽🗾🗿😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗🥲🫣🧐🥸😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮😯😰😱😲😳😴😵😶😷😸😹😺😻😼😽😾😿🙀🙁🙂🙃🙄🙅🙆🙇🙈🙉🙊🙋🙌🙍🙎🙏🪆🚀🚁🚂🚃🚄🚅🚆🚇🚈🚉🚊🚋🚌🚍🚎🚏🚐🚑🚒🚓🚔🚕🚖🚗🚘🚙🚚🚛🚜🚝🚞🚟🚠🚡🚢🚣🚤🚥🚦🚧🚨🚩🚪🚫🚬🚭🚮🚯🚰🚱🚲🚳🚴🚵🚶🚷🚸🚹🚺🚻🚼🚽🚾🚿🛀🛁🛂🛃🛄🛅🛋🛌🛍🛎🛏🛐🛑🛒🛠🛡🛢🛣🛤🛥🛩🛫🛬🛰🛳🛴🛵🛶🤐🤑🤒🤓🤔🤕🤖🤗🤘🤙🤚🤛🤜🤝🤞🤠🤡🤢🤣🤤🤥🤦🤧🤰🤳🤴🤵🤶🤷🤸🤹🤺🤼🤽🤾🥀🥁🥂🥃🥄🥅🥇🥈🥉🥊🥋🥐🥑🥒🥓🥔🥕🥖🥗🥘🥙🥚🥛🥜🥝🥞🦀🦁🦂🦃🦄🦅🦆🦇🦈🦉🦊🦋🦌🦍🦎🦏🦐🦑🧀🦖🦗🦧🦮🦯🦴🦵🦶🦷🦸🦹🦺🦻🦼🦽🦾🦿🧀🧁🧂🧃🧄🧅🧆🧇🧈🧉🧊🧋🧍🧎🧏🧐🧑🧒🧓🧔🧕🧖🧗🧘🧙🧚🧛🧜🧝🧞🧟🧠🧡🧢🧣🧤🧥🧦🧧🧨🧩🧪🧫🧬🧭🧮🧯🧰🧱🧲🧳🧴🧵🧶🧷🧸🧹🧺🧻🧼🧽🧾🧿🩰🩱🩲🩳🩴🩸🩹🩺🪀🪁🪂🪃🪄🪅🪆🪐🪑🪒🪓🪔🪕🪖🪗🪘🪙🪚🪛🪜🪝🪞🪟🪠🪡🪢🪣🪤🪥🪦🪧🪨🪩🪪🪫🪬🪭🪮🪯🪰🪱🪲🪳🪴🪵🪶🪷🪸🪹🪺🪻🪼🪽🪿🫀🫁🫂🫃🫄🫅🫎🫏🫐🫑🫒🫓🫔🫕🫖🫗🫘🫙🫚🫛🫠🫡🫢🫣🫤🫥🫦🫧🫨🫰🫱🫲🫳🫴🫵🫶🫷🫸🦥🥦]" 10 | -------------------------------------------------------------------------------- /Mac/Helpers/FileWatcher.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class FileWatcher { 4 | let filePaths: [String] // -- paths to watch - works on folders and file paths 5 | 6 | var callback: ((_ fileWatcherEvent: FileWatcherEvent) -> Void)? 7 | var queue: DispatchQueue? 8 | 9 | private var streamRef: FSEventStreamRef? 10 | private var hasStarted: Bool { streamRef != nil } 11 | 12 | init(_ paths: [String]) { filePaths = paths } 13 | 14 | /** 15 | * Start listening for FSEvents 16 | */ 17 | func start() { 18 | guard !hasStarted else { return } // -- make sure we are not already listening! 19 | 20 | var context = FSEventStreamContext( 21 | version: 0, info: Unmanaged.passUnretained(self).toOpaque(), 22 | retain: retainCallback, release: releaseCallback, 23 | copyDescription: nil 24 | ) 25 | 26 | streamRef = FSEventStreamCreate( 27 | kCFAllocatorDefault, eventCallback, &context, 28 | filePaths as CFArray, FSEventStreamEventId(kFSEventStreamEventIdSinceNow), 0, 29 | UInt32(kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents) 30 | ) 31 | 32 | selectStreamScheduler() 33 | FSEventStreamStart(streamRef!) 34 | } 35 | 36 | /** 37 | * Stop listening for FSEvents 38 | */ 39 | func stop() { 40 | guard hasStarted else { return } // -- make sure we are indeed listening! 41 | 42 | FSEventStreamStop(streamRef!) 43 | FSEventStreamInvalidate(streamRef!) 44 | FSEventStreamRelease(streamRef!) 45 | 46 | streamRef = nil 47 | } 48 | 49 | private let eventCallback: FSEventStreamCallback = { 50 | _, clientCallBackInfo, numEvents, eventPaths, eventFlags, eventIds 51 | in 52 | let fileSystemWatcher = Unmanaged.fromOpaque(clientCallBackInfo!).takeUnretainedValue() 53 | let paths = Unmanaged.fromOpaque(eventPaths).takeUnretainedValue() as! [String] 54 | 55 | for index in 0 ..< numEvents { 56 | fileSystemWatcher.callback?(FileWatcherEvent(eventIds[index], paths[index], eventFlags[index])) 57 | } 58 | } 59 | 60 | private let retainCallback: CFAllocatorRetainCallBack = { (info: UnsafeRawPointer?) in 61 | _ = Unmanaged.fromOpaque(info!).retain() 62 | return info 63 | } 64 | 65 | private let releaseCallback: CFAllocatorReleaseCallBack = { (info: UnsafeRawPointer?) in 66 | Unmanaged.fromOpaque(info!).release() 67 | } 68 | 69 | private func selectStreamScheduler() { 70 | if let queue = queue { 71 | FSEventStreamSetDispatchQueue(streamRef!, queue) 72 | } else { 73 | FSEventStreamScheduleWithRunLoop( 74 | streamRef!, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue 75 | ) 76 | } 77 | } 78 | } 79 | 80 | extension FileWatcher { 81 | convenience init(_ paths: [String], _ callback: @escaping (_ fileWatcherEvent: FileWatcherEvent) -> Void) { 82 | self.init(paths) 83 | self.callback = callback 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Mac/Helpers/FileWatcherEvent.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * PARAM: id: is an id number that the os uses to differentiate between events. 5 | * PARAM: path: is the path the change took place. its formatted like so: Users/John/Desktop/test/text.txt 6 | * PARAM: flag: pertains to the file event type. 7 | * EXAMPLE: let url = NSURL(fileURLWithPath: event.path)//<--formats paths to: file:///Users/John/Desktop/test/text.txt 8 | * EXAMPLE: Swift.print("fileWatcherEvent.fileChange: " + "\(event.fileChange)") 9 | * EXAMPLE: Swift.print("fileWatcherEvent.fileModified: " + "\(event.fileModified)") 10 | * EXAMPLE: Swift.print("\t eventId: \(event.id) - eventFlags: \(event.flags) - eventPath: \(event.path)") 11 | */ 12 | class FileWatcherEvent { 13 | var id: FSEventStreamEventId 14 | var path: String 15 | var flags: FSEventStreamEventFlags 16 | 17 | init(_ eventId: FSEventStreamEventId, _ eventPath: String, _ eventFlags: FSEventStreamEventFlags) { 18 | id = eventId 19 | path = eventPath 20 | flags = eventFlags 21 | } 22 | } 23 | 24 | /** 25 | * The following code is to differentiate between the FSEvent flag types (aka file event types) 26 | * NOTE: Be aware that .DS_STORE changes frequently when other files change 27 | */ 28 | extension FileWatcherEvent { 29 | /* general */ 30 | var fileChange: Bool { (flags & FSEventStreamEventFlags(kFSEventStreamEventFlagItemIsFile)) != 0 } 31 | var dirChange: Bool { (flags & FSEventStreamEventFlags(kFSEventStreamEventFlagItemIsDir)) != 0 } 32 | /* CRUD */ 33 | var created: Bool { (flags & FSEventStreamEventFlags(kFSEventStreamEventFlagItemCreated)) != 0 } 34 | var removed: Bool { (flags & FSEventStreamEventFlags(kFSEventStreamEventFlagItemRemoved)) != 0 } 35 | var renamed: Bool { (flags & FSEventStreamEventFlags(kFSEventStreamEventFlagItemRenamed)) != 0 } 36 | var modified: Bool { (flags & FSEventStreamEventFlags(kFSEventStreamEventFlagItemModified)) != 0 } 37 | } 38 | 39 | /** 40 | * Convenience 41 | */ 42 | extension FileWatcherEvent { 43 | /* File */ 44 | var fileCreated: Bool { fileChange && created } 45 | var fileRemoved: Bool { fileChange && removed } 46 | var fileRenamed: Bool { fileChange && renamed } 47 | var fileModified: Bool { fileChange && modified } 48 | /* Directory */ 49 | var dirCreated: Bool { dirChange && created } 50 | var dirRemoved: Bool { dirChange && removed } 51 | var dirRenamed: Bool { dirChange && renamed } 52 | var dirModified: Bool { dirChange && modified } 53 | } 54 | 55 | /** 56 | * Simplifies debugging 57 | * EXAMPLE: Swift.print(event.description)//Outputs: The file /Users/John/Desktop/test/text.txt was modified 58 | */ 59 | extension FileWatcherEvent { 60 | var description: String { 61 | var result = "The \(fileChange ? "file" : "directory") \(path) was" 62 | if created { 63 | result += " created" 64 | } 65 | if removed { 66 | result += " removed" 67 | } 68 | if renamed { 69 | result += " renamed" 70 | } 71 | if modified { 72 | result += " modified" 73 | } 74 | return result 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Mac/Helpers/KeychainConfiguration.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | A simple struct that defines the service and access group to be used by the sample apps. 7 | */ 8 | 9 | import Foundation 10 | 11 | struct KeychainConfiguration { 12 | static let serviceName = "MiaoYanApp" 13 | static let accessGroup: String? = nil 14 | } 15 | -------------------------------------------------------------------------------- /Mac/Helpers/NameHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class NameHelper { 4 | public static func getUniqueFileName(name: String, postfix: Int = 0, project: Project, ext: String) -> URL { 5 | let defaultName = UUID().uuidString 6 | var postfix = postfix 7 | var name = name 8 | .trimmingCharacters(in: CharacterSet.whitespaces) 9 | .replacingOccurrences(of: ":", with: "-") 10 | .replacingOccurrences(of: "/", with: ":") 11 | 12 | if name.isEmpty { 13 | name = defaultName 14 | } 15 | 16 | var fileUrl = project.url 17 | fileUrl.appendPathComponent(name) 18 | fileUrl.appendPathExtension(ext) 19 | 20 | let fileManager = FileManager.default 21 | if fileManager.fileExists(atPath: fileUrl.path) { 22 | let regex = try? NSRegularExpression(pattern: "(.+)\\s(\\d)+", options: .caseInsensitive) 23 | 24 | if let result = regex?.firstMatch(in: name, range: NSRange(0 ..< name.count)) { 25 | if let range = Range(result.range(at: 1), in: name) { 26 | name = String(name[range]) 27 | } 28 | 29 | if let range = Range(result.range(at: 2), in: name) { 30 | let digit = name[range] 31 | 32 | if let converted = Int(digit) { 33 | postfix = converted 34 | } 35 | } 36 | } 37 | 38 | let increment = postfix + 1 39 | let newName = name + " " + String(increment) 40 | return NameHelper.getUniqueFileName(name: newName, postfix: increment, project: project, ext: ext) 41 | } 42 | 43 | return fileUrl 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Mac/Helpers/UserDataService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDataService.swift 3 | // FSNotes 4 | // 5 | // Created by Oleksandr Glushchenko on 1/30/18. 6 | // Copyright © 2018 Oleksandr Glushchenko. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class UserDataService { 12 | public static let instance = UserDataService() 13 | 14 | private var _searchTrigger = false 15 | private var _lastRenamed: URL? 16 | private var _fsUpdates = false 17 | private var _isNotesTableEscape = false 18 | private var _isDark = false 19 | 20 | private var _lastType: Int? 21 | private var _lastProject: URL? 22 | private var _lastName: String? 23 | 24 | private var _importProgress = false 25 | 26 | public var searchTrigger: Bool { 27 | get { 28 | _searchTrigger 29 | } 30 | set { 31 | _searchTrigger = newValue 32 | } 33 | } 34 | 35 | public var focusOnImport: URL? { 36 | get { 37 | _lastRenamed 38 | } 39 | set { 40 | _lastRenamed = newValue 41 | } 42 | } 43 | 44 | public var fsUpdatesDisabled: Bool { 45 | get { 46 | _fsUpdates 47 | } 48 | set { 49 | _fsUpdates = newValue 50 | } 51 | } 52 | 53 | public var isNotesTableEscape: Bool { 54 | get { 55 | _isNotesTableEscape 56 | } 57 | set { 58 | _isNotesTableEscape = newValue 59 | } 60 | } 61 | 62 | public var isDark: Bool { 63 | get { 64 | _isDark 65 | } 66 | set { 67 | _isDark = newValue 68 | } 69 | } 70 | 71 | public var lastType: Int? { 72 | get { 73 | _lastType 74 | } 75 | set { 76 | _lastType = newValue 77 | } 78 | } 79 | 80 | public var lastName: String? { 81 | get { 82 | _lastName 83 | } 84 | set { 85 | _lastName = newValue 86 | } 87 | } 88 | 89 | public var lastProject: URL? { 90 | get { 91 | _lastProject 92 | } 93 | set { 94 | _lastProject = newValue 95 | } 96 | } 97 | 98 | public func resetLastSidebar() { 99 | _lastProject = nil 100 | _lastType = nil 101 | _lastName = nil 102 | } 103 | 104 | public var skipSidebarSelection: Bool { 105 | get { 106 | _importProgress 107 | } 108 | set { 109 | _importProgress = newValue 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon_512x512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/code.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom": "universal", 9 | "color": { 10 | "color-space": "srgb", 11 | "components": { 12 | "red": "0.97", 13 | "alpha": "1.00", 14 | "blue": "0.97", 15 | "green": "0.97" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom": "universal", 21 | "appearances": [ 22 | { 23 | "appearance": "luminosity", 24 | "value": "dark" 25 | } 26 | ], 27 | "color": { 28 | "color-space": "srgb", 29 | "components": { 30 | "red": "0.16", 31 | "alpha": "1.000", 32 | "blue": "0.20", 33 | "green": "0.180" 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/divider.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.9", 13 | "alpha" : "1.000", 14 | "blue" : "0.9", 15 | "green" : "0.9" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.07", 31 | "alpha" : "1.000", 32 | "blue" : "0.07", 33 | "green" : "0.07" 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/highlight.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "alpha" : "1.000", 13 | "red" : "0.05", 14 | "blue" : "0.85", 15 | "green" : "0.41" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "alpha" : "1.000", 31 | "red" : "0.11", 32 | "blue" : "0.94", 33 | "green" : "0.61" 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/home.png.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "home.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "home-white.png", 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "filename" : "home.png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "idiom" : "universal", 26 | "filename" : "home-white.png", 27 | "appearances" : [ 28 | { 29 | "appearance" : "luminosity", 30 | "value" : "dark" 31 | } 32 | ], 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "universal", 37 | "filename" : "home.png", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "idiom" : "universal", 42 | "filename" : "home-white.png", 43 | "scale" : "3x", 44 | "appearances" : [ 45 | { 46 | "appearance" : "luminosity", 47 | "value" : "dark" 48 | } 49 | ] 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /Mac/Images.xcassets/home.png.imageset/home-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/home.png.imageset/home-white.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/home.png.imageset/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/home.png.imageset/home.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/html.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "srgb", 6 | "components": { 7 | "alpha": "1.000", 8 | "blue": "0.13", 9 | "green": "0.54", 10 | "red": "0.95" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "srgb", 24 | "components": { 25 | "alpha": "1.000", 26 | "blue": "0.52", 27 | "green": "0.79", 28 | "red": "1.00" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "xcode", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/link.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.60", 9 | "green" : "0.65", 10 | "red" : "0.02" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.79", 27 | "green" : "1.00", 28 | "red" : "0.38" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/list.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.51", 13 | "alpha" : "1.000", 14 | "blue" : "0.16", 15 | "green" : "0.42" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.77", 31 | "alpha" : "1.000", 32 | "blue" : "0.77", 33 | "green" : "0.78" 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/mainBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0xFF", 13 | "alpha" : "1.000", 14 | "blue" : "0xFF", 15 | "green" : "0xFF" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.130", 31 | "alpha" : "1.000", 32 | "blue" : "0.170", 33 | "green" : "0.150" 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/mainText.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.150", 13 | "alpha" : "1.000", 14 | "blue" : "0.150", 15 | "green" : "0.150" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.91", 31 | "alpha" : "1.000", 32 | "blue" : "0.92", 33 | "green" : "0.91" 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/makeNoteAsset.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "blank.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "blank.png", 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "filename" : "blank.png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "idiom" : "universal", 26 | "filename" : "blank.png", 27 | "appearances" : [ 28 | { 29 | "appearance" : "luminosity", 30 | "value" : "dark" 31 | } 32 | ], 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "universal", 37 | "filename" : "blank.png", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "idiom" : "universal", 42 | "filename" : "blank.png", 43 | "appearances" : [ 44 | { 45 | "appearance" : "luminosity", 46 | "value" : "dark" 47 | } 48 | ], 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | }, 56 | "properties" : { 57 | "template-rendering-intent" : "original" 58 | } 59 | } -------------------------------------------------------------------------------- /Mac/Images.xcassets/makeNoteAsset.imageset/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/makeNoteAsset.imageset/blank.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/new.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "quill-pen-line.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "quill-pen-line-white.png", 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "filename" : "quill-pen-line.png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "idiom" : "universal", 26 | "filename" : "quill-pen-line-white.png", 27 | "appearances" : [ 28 | { 29 | "appearance" : "luminosity", 30 | "value" : "dark" 31 | } 32 | ], 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "universal", 37 | "filename" : "quill-pen-line.png", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "idiom" : "universal", 42 | "scale" : "3x", 43 | "filename" : "quill-pen-line-white.png", 44 | "appearances" : [ 45 | { 46 | "appearance" : "luminosity", 47 | "value" : "dark" 48 | } 49 | ] 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /Mac/Images.xcassets/new.imageset/quill-pen-line-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/new.imageset/quill-pen-line-white.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/new.imageset/quill-pen-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/new.imageset/quill-pen-line.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/new_rep.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "folder-add-line.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "folder-add-line-white.png", 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "filename" : "folder-add-line.png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "idiom" : "universal", 26 | "filename" : "folder-add-line-white.png", 27 | "appearances" : [ 28 | { 29 | "appearance" : "luminosity", 30 | "value" : "dark" 31 | } 32 | ], 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "universal", 37 | "filename" : "folder-add-line.png", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "idiom" : "universal", 42 | "scale" : "3x", 43 | "filename" : "folder-add-line-white.png", 44 | "appearances" : [ 45 | { 46 | "appearance" : "luminosity", 47 | "value" : "dark" 48 | } 49 | ] 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /Mac/Images.xcassets/new_rep.imageset/folder-add-line-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/new_rep.imageset/folder-add-line-white.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/new_rep.imageset/folder-add-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/new_rep.imageset/folder-add-line.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/pin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "pin.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "pin-white.png", 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "filename" : "pin.png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "idiom" : "universal", 26 | "filename" : "pin-white.png", 27 | "appearances" : [ 28 | { 29 | "appearance" : "luminosity", 30 | "value" : "dark" 31 | } 32 | ], 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "universal", 37 | "filename" : "pin.png", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "idiom" : "universal", 42 | "scale" : "3x", 43 | "filename" : "pin-white.png", 44 | "appearances" : [ 45 | { 46 | "appearance" : "luminosity", 47 | "value" : "dark" 48 | } 49 | ] 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/pin.imageset/pin-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/pin.imageset/pin-white.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/pin.imageset/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/pin.imageset/pin.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/repository.png.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cell.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cell-white.png", 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "filename" : "cell.png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "idiom" : "universal", 26 | "filename" : "cell-white.png", 27 | "appearances" : [ 28 | { 29 | "appearance" : "luminosity", 30 | "value" : "dark" 31 | } 32 | ], 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "universal", 37 | "filename" : "cell.png", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "idiom" : "universal", 42 | "scale" : "3x", 43 | "filename" : "cell-white.png", 44 | "appearances" : [ 45 | { 46 | "appearance" : "luminosity", 47 | "value" : "dark" 48 | } 49 | ] 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /Mac/Images.xcassets/repository.png.imageset/cell-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/repository.png.imageset/cell-white.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/repository.png.imageset/cell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/repository.png.imageset/cell.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/reverseBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x2A", 13 | "alpha" : "1.000", 14 | "blue" : "0x2E", 15 | "green" : "0x2B" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0xFF", 31 | "alpha" : "1.000", 32 | "blue" : "0xFF", 33 | "green" : "0xFF" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Mac/Images.xcassets/title.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "components": { 6 | "alpha": "1.000", 7 | "blue": "0.68", 8 | "green": "0.24", 9 | "red": "0.48" 10 | } 11 | }, 12 | "idiom": "universal" 13 | }, 14 | { 15 | "appearances": [ 16 | { 17 | "appearance": "luminosity", 18 | "value": "dark" 19 | } 20 | ], 21 | "color": { 22 | "color-space": "srgb", 23 | "components": { 24 | "alpha": "1.000", 25 | "blue": "1.00", 26 | "green": "0.47", 27 | "red": "0.63" 28 | } 29 | }, 30 | "idiom": "universal" 31 | } 32 | ], 33 | "info": { 34 | "author": "xcode", 35 | "version": 1 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/trash.png.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "garbage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "garbage-white.png", 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "filename" : "garbage.png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "idiom" : "universal", 26 | "filename" : "garbage-white.png", 27 | "appearances" : [ 28 | { 29 | "appearance" : "luminosity", 30 | "value" : "dark" 31 | } 32 | ], 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "universal", 37 | "filename" : "garbage.png", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "idiom" : "universal", 42 | "scale" : "3x", 43 | "filename" : "garbage-white.png", 44 | "appearances" : [ 45 | { 46 | "appearance" : "luminosity", 47 | "value" : "dark" 48 | } 49 | ] 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /Mac/Images.xcassets/trash.png.imageset/garbage-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/trash.png.imageset/garbage-white.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/trash.png.imageset/garbage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/trash.png.imageset/garbage.png -------------------------------------------------------------------------------- /Mac/Images.xcassets/underlineColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.000", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/wand.and.rays.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "wand_rays.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "wand_rays.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "wand_rays.png" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Mac/Images.xcassets/wand.and.rays.imageset/wand_rays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tw93/MiaoYan/dc645b659a21cf8a7bc1583460c2d6b40b415647/Mac/Images.xcassets/wand.and.rays.imageset/wand_rays.png -------------------------------------------------------------------------------- /Mac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ATSApplicationFontsPath 6 | . 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | $(PRODUCT_NAME) 11 | CFBundleDocumentTypes 12 | 13 | 14 | CFBundleTypeExtensions 15 | 16 | md 17 | markdown 18 | 19 | CFBundleTypeIconFile 20 | Markdown 21 | CFBundleTypeName 22 | Markdown 23 | CFBundleTypeRole 24 | Editor 25 | LSHandlerRank 26 | Default 27 | LSItemContentTypes 28 | 29 | net.daringfireball.markdown 30 | 31 | 32 | 33 | CFBundleExecutable 34 | $(EXECUTABLE_NAME) 35 | CFBundleGetInfoString 36 | 37 | CFBundleIdentifier 38 | $(PRODUCT_BUNDLE_IDENTIFIER) 39 | CFBundleInfoDictionaryVersion 40 | 6.0 41 | CFBundleName 42 | $(PRODUCT_NAME) 43 | CFBundlePackageType 44 | APPL 45 | CFBundleShortVersionString 46 | $(MARKETING_VERSION) 47 | CFBundleURLTypes 48 | 49 | 50 | CFBundleTypeRole 51 | Viewer 52 | CFBundleURLIconFile 53 | icon 54 | CFBundleURLName 55 | com.tw93.miaoyan 56 | CFBundleURLSchemes 57 | 58 | miaoyan 59 | 60 | 61 | 62 | CFBundleTypeRole 63 | Viewer 64 | CFBundleURLIconFile 65 | icon 66 | CFBundleURLName 67 | com.tw93.miaoyan 68 | CFBundleURLSchemes 69 | 70 | nv 71 | 72 | 73 | 74 | CFBundleTypeRole 75 | Viewer 76 | CFBundleURLIconFile 77 | icon 78 | CFBundleURLName 79 | com.tw93.miaoyan 80 | CFBundleURLSchemes 81 | 82 | nvalt 83 | 84 | 85 | 86 | CFBundleVersion 87 | $(CURRENT_PROJECT_VERSION) 88 | LSApplicationCategoryType 89 | public.app-category.productivity 90 | LSMinimumSystemVersion 91 | $(MACOSX_DEPLOYMENT_TARGET) 92 | LSUIElement 93 | 94 | NSAppTransportSecurity 95 | 96 | NSAllowsArbitraryLoads 97 | 98 | NSExceptionDomains 99 | 100 | 127.0.0.1:36677 101 | 102 | 103 | 104 | NSMainStoryboardFile 105 | Main 106 | NSPrincipalClass 107 | NSApplication 108 | NSUbiquitousContainers 109 | 110 | iCloud.com.tw93.miaoyan 111 | 112 | NSUbiquitousContainerIsDocumentScopePublic 113 | 114 | NSUbiquitousContainerName 115 | MiaoYan 116 | NSUbiquitousContainerSupportedFolderLevels 117 | One 118 | 119 | 120 | SUAllowsAutomaticUpdates 121 | 122 | SUEnableAutomaticChecks 123 | 124 | SUFeedURL 125 | https://miaoyan.app/appcast.xml 126 | SUPublicEDKey 127 | 4IB5REPPZ6ya7EP1aIYpaleXXOEVmenFqN4DFnsVbHc= 128 | SUScheduledCheckInterval 129 | 3666 130 | UILaunchStoryboardName 131 | 132 | UTExportedTypeDeclarations 133 | 134 | 135 | UTTypeConformsTo 136 | 137 | public.plain-text 138 | 139 | UTTypeDescription 140 | Markdwon 141 | UTTypeIconFile 142 | Markdown 143 | UTTypeIdentifier 144 | net.daringfireball.markdown 145 | UTTypeTagSpecification 146 | 147 | public.filename-extension 148 | 149 | md 150 | markdown 151 | 152 | public.mime-type 153 | 154 | text/markdown 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /Mac/MainWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class MainWindow: NSWindow { 4 | override func awakeFromNib() { 5 | super.awakeFromNib() 6 | 7 | guard UserDefaults.standard.object(forKey: "NSWindow Frame myMainWindow") == nil else { return } 8 | 9 | if let screenHeight = NSScreen.main?.frame.height, let screenWidth = NSScreen.main?.frame.width { 10 | let x = (screenWidth - frame.width) / 2 11 | let y = (screenHeight - frame.height) / 2 12 | let rect = NSRect(x: x, y: y, width: frame.width, height: 680) 13 | setFrame(rect, display: true) 14 | } 15 | } 16 | 17 | override func mouseUp(with event: NSEvent) { 18 | if event.clickCount >= 2, isPointInTitleBar(point: event.locationInWindow) { 19 | performZoom(nil) 20 | } 21 | super.mouseUp(with: event) 22 | } 23 | 24 | fileprivate func isPointInTitleBar(point: CGPoint) -> Bool { 25 | if let windowFrame = contentView?.frame { 26 | let titleBarRect = NSRect(x: contentLayoutRect.origin.x, y: contentLayoutRect.origin.y + contentLayoutRect.height, width: contentLayoutRect.width, height: windowFrame.height - contentLayoutRect.height) 27 | return titleBarRect.contains(point) 28 | } 29 | return false 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Mac/MainWindowController.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | 3 | class MainWindowController: NSWindowController, NSWindowDelegate { 4 | let notesListUndoManager = UndoManager() 5 | var editorUndoManager = UndoManager() 6 | 7 | override func windowDidLoad() { 8 | let appDelegate = NSApplication.shared.delegate as! AppDelegate 9 | appDelegate.mainWindowController = self 10 | 11 | window?.isMovableByWindowBackground = true 12 | window?.hidesOnDeactivate = false 13 | window?.titleVisibility = .hidden 14 | window?.titlebarAppearsTransparent = true 15 | windowFrameAutosaveName = "myMainWindow" 16 | } 17 | 18 | func windowDidResize(_ notification: Notification) { 19 | refreshEditArea() 20 | } 21 | 22 | func makeNew() { 23 | window?.makeKeyAndOrderFront(self) 24 | NSApp.activate(ignoringOtherApps: true) 25 | refreshEditArea(focusSearch: true) 26 | } 27 | 28 | func refreshEditArea(focusSearch: Bool = false) { 29 | guard let vc = ViewController.shared() else { return } 30 | vc.editArea.updateTextContainerInset() 31 | } 32 | 33 | func windowWillReturnUndoManager(_ window: NSWindow) -> UndoManager? { 34 | guard let fr = window.firstResponder else { 35 | return notesListUndoManager 36 | } 37 | 38 | if fr.isKind(of: NotesTableView.self) { 39 | return notesListUndoManager 40 | } 41 | 42 | if fr.isKind(of: EditTextView.self) { 43 | guard let vc = ViewController.shared(), let ev = vc.editArea, ev.isEditable else { return notesListUndoManager } 44 | return editorUndoManager 45 | } 46 | 47 | return notesListUndoManager 48 | } 49 | 50 | public static func shared() -> NSWindow? { 51 | if let appDelegate = NSApplication.shared.delegate as? AppDelegate { 52 | return appDelegate.mainWindowController?.window 53 | } 54 | 55 | return nil 56 | } 57 | 58 | func windowWillEnterFullScreen(_ notification: Notification) { 59 | UserDefaultsManagement.isWillFullScreen = true 60 | } 61 | 62 | func windowWillExitFullScreen(_ notification: Notification) { 63 | UserDefaultsManagement.isWillFullScreen = false 64 | } 65 | 66 | func windowDidEnterFullScreen(_ notification: Notification) { 67 | UserDefaultsManagement.fullScreen = true 68 | } 69 | 70 | func windowDidExitFullScreen(_ notification: Notification) { 71 | UserDefaultsManagement.fullScreen = false 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Mac/PrefsViewController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import CoreData 3 | import MASShortcut 4 | 5 | class PrefsViewController: NSTabViewController { 6 | override func viewDidLoad() { 7 | title = "Preferences" 8 | super.viewDidLoad() 9 | } 10 | 11 | override func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { 12 | let toolbarItem = super.toolbar(toolbar, itemForItemIdentifier: itemIdentifier, willBeInsertedIntoToolbar: flag) 13 | 14 | if 15 | let toolbarItem = toolbarItem, 16 | let tabViewItem = tabViewItems.first(where: { ($0.identifier as? String) == itemIdentifier.rawValue }) { 17 | if let name = tabViewItem.identifier as? String, name == "git" { 18 | toolbarItem.label = "\(tabViewItem.label) " 19 | return toolbarItem 20 | } 21 | 22 | if let name = tabViewItem.identifier as? String, !["advanced", "security"].contains(name) { 23 | toolbarItem.label = "\(tabViewItem.label) " 24 | } 25 | } 26 | 27 | return toolbarItem 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Mac/PrefsWindowController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class PrefsWindowController: NSWindowController, NSWindowDelegate { 4 | override func windowDidLoad() { 5 | super.windowDidLoad() 6 | window?.delegate = self 7 | window?.title = "Preferences" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Mac/ProjectSettingsViewController.swift: -------------------------------------------------------------------------------- 1 | import Carbon.HIToolbox 2 | import Cocoa 3 | 4 | class ProjectSettingsViewController: NSViewController { 5 | private var project: Project? 6 | 7 | @IBOutlet var modificationDate: NSButton! 8 | @IBOutlet var creationDate: NSButton! 9 | @IBOutlet var titleButton: NSButton! 10 | @IBOutlet var sortByGlobal: NSButton! 11 | 12 | @IBOutlet var directionASC: NSButton! 13 | @IBOutlet var directionDESC: NSButton! 14 | 15 | @IBOutlet var showInAll: NSButton! 16 | 17 | @IBAction func sortBy(_ sender: NSButton) { 18 | guard let project = project else { return } 19 | 20 | let sortBy = SortBy(rawValue: sender.identifier!.rawValue)! 21 | if sortBy != .none { 22 | project.sortBy = sortBy 23 | } 24 | 25 | project.sortBySettings = sortBy 26 | project.saveSettings() 27 | 28 | guard let vc = ViewController.shared() else { return } 29 | vc.updateTable() 30 | } 31 | 32 | @IBAction func sortDirection(_ sender: NSButton) { 33 | guard let project = project else { return } 34 | 35 | let direction = SortDirection(rawValue: sender.identifier!.rawValue)! 36 | if project.sortBySettings != .none { 37 | project.sortDirection = direction 38 | } 39 | 40 | project.sortDirectionSettings = direction 41 | project.saveSettings() 42 | 43 | guard let vc = ViewController.shared() else { return } 44 | vc.updateTable() 45 | } 46 | 47 | @IBAction func showNotesInMainList(_ sender: NSButton) { 48 | project?.showInCommon = sender.state == .on 49 | project?.saveSettings() 50 | } 51 | 52 | @IBAction func close(_ sender: Any) { 53 | dismiss(nil) 54 | } 55 | 56 | override func keyDown(with event: NSEvent) { 57 | if event.keyCode == kVK_Return || event.keyCode == kVK_Escape { 58 | dismiss(nil) 59 | } 60 | } 61 | 62 | public func load(project: Project) { 63 | showInAll.state = project.showInCommon ? .on : .off 64 | modificationDate.state = project.sortBySettings == .modificationDate ? .on : .off 65 | creationDate.state = project.sortBySettings == .creationDate ? .on : .off 66 | titleButton.state = project.sortBySettings == .title ? .on : .off 67 | sortByGlobal.state = project.sortBySettings == .none ? .on : .off 68 | 69 | directionASC.state = project.sortDirectionSettings == .asc ? .on : .off 70 | directionDESC.state = project.sortDirectionSettings == .desc ? .on : .off 71 | 72 | self.project = project 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Mac/View/EditorScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorScrollView.swift 3 | // FSNotes 4 | // 5 | // Created by Oleksandr Glushchenko on 10/7/18. 6 | // Copyright © 2018 Oleksandr Glushchenko. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class EditorScrollView: NSScrollView { 12 | private var initialHeight: CGFloat? 13 | 14 | override var isFindBarVisible: Bool { 15 | set { 16 | if let clip = subviews.first as? NSClipView { 17 | guard let currentHeight = findBarView?.frame.height else { return } 18 | 19 | clip.contentInsets.top = newValue ? CGFloat(currentHeight) : 0 20 | if newValue, let documentView = documentView { 21 | documentView.scroll(NSPoint(x: 0, y: CGFloat(-currentHeight))) 22 | } 23 | } 24 | 25 | super.isFindBarVisible = newValue 26 | } 27 | get { 28 | super.isFindBarVisible 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Mac/View/EditorSplitView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class EditorSplitView: NSSplitView, NSSplitViewDelegate { 4 | public var shouldHideDivider = false 5 | 6 | override func draw(_ dirtyRect: NSRect) { 7 | delegate = self 8 | super.draw(dirtyRect) 9 | } 10 | 11 | override func minPossiblePositionOfDivider(at dividerIndex: Int) -> CGFloat { 0 } 12 | 13 | func splitViewDidResizeSubviews(_ notification: Notification) { 14 | ViewController.shared()?.viewDidResize() 15 | } 16 | 17 | func splitViewWillResizeSubviews(_ notification: Notification) { 18 | if let vc = ViewController.shared() { 19 | vc.editArea.updateTextContainerInset() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Mac/View/EditorView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class EditorView: NSView { 4 | override func draw(_ dirtyRect: NSRect) { 5 | super.draw(dirtyRect) 6 | 7 | if UserDefaultsManagement.appearanceType != AppearanceType.Custom, #available(OSX 10.13, *) { 8 | NSColor(named: "mainBackground")!.setFill() 9 | __NSRectFill(dirtyRect) 10 | } else { 11 | layer?.backgroundColor = NSColor.white.cgColor 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Mac/View/NameTextField.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class NameTextField: NSTextField { 4 | override func becomeFirstResponder() -> Bool { 5 | let status = super.becomeFirstResponder() 6 | 7 | if UserDefaultsManagement.appearanceType != AppearanceType.Custom, #available(OSX 10.13, *) { 8 | textColor = NSColor(named: "mainText") 9 | } else { 10 | textColor = NSColor.black 11 | } 12 | 13 | return status 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Mac/View/NoteCellView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class NoteCellView: NSTableCellView { 4 | @IBOutlet var name: NSTextField! 5 | @IBOutlet var date: NSTextField! 6 | @IBOutlet var pin: NSImageView! 7 | 8 | public var note: Note? 9 | public var timestamp: Int64? 10 | public let cellSpacing: CGFloat = 34 11 | 12 | private let labelColor = NSColor(deviceRed: 0.6, green: 0.6, blue: 0.6, alpha: 1) 13 | 14 | public var tableView: NotesTableView? { 15 | guard let vc = ViewController.shared() else { return nil } 16 | 17 | return vc.notesTableView 18 | } 19 | 20 | override func draw(_ dirtyRect: NSRect) { 21 | super.draw(dirtyRect) 22 | renderPin() 23 | updateSelectionHighlight() 24 | } 25 | 26 | public func configure(note: Note) { 27 | self.note = note 28 | } 29 | 30 | 31 | // these views' color when the cell is selected. 32 | override var backgroundStyle: NSView.BackgroundStyle { 33 | set { 34 | updateSelectionHighlight() 35 | } 36 | get { 37 | super.backgroundStyle 38 | } 39 | } 40 | 41 | public func updateSelectionHighlight() { 42 | 43 | // 字体和间距 44 | name.font = UserDefaultsManagement.nameFont 45 | date.font = UserDefaultsManagement.dateFont 46 | name.addCharacterSpacing() 47 | date.addCharacterSpacing() 48 | 49 | if backgroundStyle == NSView.BackgroundStyle.dark { 50 | date.textColor = NSColor.white 51 | name.textColor = NSColor.white 52 | } else { 53 | date.textColor = labelColor 54 | if #available(OSX 10.13, *) { 55 | name.textColor = NSColor(named: "mainText") 56 | } else { 57 | name.textColor = NSColor.black 58 | } 59 | } 60 | } 61 | 62 | func renderPin() { 63 | if let value = objectValue, let note = value as? Note { 64 | pin.image = NSImage(named: "pin") 65 | pin.isHidden = !note.isPinned 66 | } 67 | 68 | adjustPinPosition() 69 | } 70 | 71 | func hasTitle() -> Bool { 72 | if let value = objectValue, let note = value as? Note { 73 | return !note.title.isEmpty 74 | } else { 75 | return false 76 | } 77 | } 78 | 79 | public func adjustPinPosition() { 80 | for constraint in constraints { 81 | if constraint.secondAttribute == .leading, let im = constraint.firstItem as? NSImageView { 82 | if im.identifier?.rawValue == "pin" { 83 | if let note = objectValue as? Note, !note.showIconInList() { 84 | constraint.constant = -17 85 | } else { 86 | constraint.constant = 3 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | private func adjustTopMargin(margin: CGFloat) { 94 | for constraint in constraints { 95 | if constraint.secondAttribute == .top, let item = constraint.firstItem { 96 | if let firstItem = item as? NSImageView, firstItem.identifier?.rawValue == "pin" { 97 | constraint.constant = margin - 1 98 | continue 99 | } 100 | if item.isKind(of: NameTextField.self) { 101 | constraint.constant = margin 102 | continue 103 | } 104 | 105 | if let item = item as? NSTextField, item.identifier?.rawValue == "cellDate" { 106 | constraint.constant = margin 107 | } 108 | } 109 | } 110 | } 111 | 112 | public func attachHeaders(note: Note) { 113 | if let title = note.getTitle() { 114 | name.stringValue = title 115 | } 116 | 117 | if let viewController = ViewController.shared(), 118 | let sidebarItem = viewController.getSidebarItem(), 119 | let sort = sidebarItem.project?.sortBy, 120 | sort == .creationDate, 121 | let date = note.getCreationDateForLabel() { 122 | self.date.stringValue = date 123 | } else { 124 | date.stringValue = note.getDateForLabel() 125 | } 126 | updateSelectionHighlight() 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Mac/View/NoteRowView.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Cocoa 3 | 4 | class NoteRowView: NSTableRowView { 5 | override func draw(_ dirtyRect: NSRect) { 6 | super.draw(dirtyRect) 7 | 8 | // 选中的时候不出现分割线 9 | if isSelected { return } 10 | 11 | // 选中的行上一个分割线也不出现 12 | if let tableView = superview as? NSTableView, 13 | let selectedRow = tableView.selectedRowIndexes.first, selectedRow > 0, 14 | let previousRow = tableView.rowView(atRow: selectedRow - 1, makeIfNecessary: false), 15 | self == previousRow { 16 | return 17 | } 18 | 19 | drawSeparator(in: dirtyRect) 20 | } 21 | 22 | override var isEmphasized: Bool { 23 | set {} 24 | get { 25 | false 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Mac/View/OutlineHeaderView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class OutlineHeaderView: NSView { 4 | var onMouseEnteredClosure: (()->())? 5 | var onMouseExitedClosure: (()->())? 6 | 7 | override func draw(_ dirtyRect: NSRect) { 8 | super.draw(dirtyRect) 9 | 10 | if UserDefaultsManagement.appearanceType != AppearanceType.Custom, #available(OSX 10.13, *) { 11 | NSColor(named: "mainBackground")!.setFill() 12 | __NSRectFill(dirtyRect) 13 | } else { 14 | layer?.backgroundColor = NSColor.white.cgColor 15 | } 16 | } 17 | 18 | override func awakeFromNib() { 19 | addTrackingArea(NSTrackingArea(rect: bounds, options: [.activeAlways, .mouseEnteredAndExited], owner: self, userInfo: nil)) 20 | } 21 | 22 | override func layout() { 23 | super.layout() 24 | 25 | trackingAreas.forEach { [weak self] area in 26 | self?.removeTrackingArea(area) 27 | } 28 | 29 | addTrackingArea(NSTrackingArea(rect: bounds, options: [.activeAlways, .mouseEnteredAndExited], owner: self, userInfo: nil)) 30 | } 31 | 32 | override func mouseEntered(with event: NSEvent) { 33 | onMouseEnteredClosure?() 34 | } 35 | 36 | override func mouseExited(with event: NSEvent) { 37 | onMouseExitedClosure?() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Mac/View/PreviewTextField.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class PreviewTextField: NSTextField { 4 | override var intrinsicContentSize: NSSize { 5 | if maximumNumberOfLines == -1 { 6 | let width = super.intrinsicContentSize.width 7 | 8 | return NSSize(width: width, height: 0) 9 | } 10 | 11 | return super.intrinsicContentSize 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Mac/View/SharingService.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | extension ViewController: NSSharingServicePickerDelegate { 4 | func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, sharingServicesForItems items: [Any], proposedSharingServices proposedServices: [NSSharingService]) -> [NSSharingService] { 5 | guard let image = NSImage(named: "copy.png") else { 6 | return proposedServices 7 | } 8 | 9 | var share = proposedServices 10 | let titlePlain = NSLocalizedString("Copy Plain Text", comment: "") 11 | let plainText = NSSharingService(title: titlePlain, image: image, alternateImage: image, handler: { 12 | self.saveTextAtClipboard() 13 | }) 14 | share.insert(plainText, at: 0) 15 | 16 | let titleHTML = NSLocalizedString("Copy HTML", comment: "") 17 | let html = NSSharingService(title: titleHTML, image: image, alternateImage: image, handler: { 18 | self.saveHtmlAtClipboard() 19 | }) 20 | share.insert(html, at: 1) 21 | 22 | return share 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Mac/View/SidebarCellView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class SidebarCellView: NSTableCellView { 4 | @IBOutlet var icon: NSImageView! 5 | @IBOutlet var label: NSTextField! 6 | 7 | var storage = Storage.sharedInstance() 8 | 9 | override func draw(_ dirtyRect: NSRect) { 10 | label.font = UserDefaultsManagement.nameFont 11 | super.draw(dirtyRect) 12 | } 13 | 14 | private var trackingArea: NSTrackingArea? 15 | 16 | override func updateTrackingAreas() { 17 | if let trackingArea = self.trackingArea { 18 | removeTrackingArea(trackingArea) 19 | } 20 | 21 | let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeAlways] 22 | let trackingArea = NSTrackingArea(rect: bounds, options: options, owner: self, userInfo: nil) 23 | addTrackingArea(trackingArea) 24 | } 25 | 26 | @IBAction func projectName(_ sender: NSTextField) { 27 | let cell = sender.superview as? SidebarCellView 28 | guard let si = cell?.objectValue as? SidebarItem, let project = si.project else { return } 29 | 30 | let newURL = project.url.deletingLastPathComponent().appendingPathComponent(sender.stringValue) 31 | 32 | do { 33 | try FileManager.default.moveItem(at: project.url, to: newURL) 34 | project.url = newURL 35 | project.label = newURL.lastPathComponent 36 | 37 | } catch { 38 | sender.stringValue = project.url.lastPathComponent 39 | let alert = NSAlert() 40 | alert.messageText = error.localizedDescription 41 | alert.runModal() 42 | } 43 | 44 | guard let vc = window?.contentViewController as? ViewController else { return } 45 | vc.storage.removeBy(project: project) 46 | vc.storage.loadLabel(project) 47 | vc.updateTable() 48 | } 49 | 50 | @IBAction func add(_ sender: Any) { 51 | guard let vc = ViewController.shared() else { return } 52 | vc.storageOutlineView.addProject(self) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Mac/View/SidebarNotesView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class SidebarNotesView: NSView { 4 | override func draw(_ dirtyRect: NSRect) { 5 | super.draw(dirtyRect) 6 | 7 | if UserDefaultsManagement.appearanceType != AppearanceType.Custom, #available(OSX 10.13, *) { 8 | NSColor(named: "mainBackground")!.setFill() 9 | __NSRectFill(dirtyRect) 10 | } else { 11 | layer?.backgroundColor = NSColor.white.cgColor 12 | } 13 | } 14 | 15 | override func awakeFromNib() { 16 | var f = frame 17 | f.size.width = 280 18 | frame = f 19 | setFrameSize(f.size) 20 | setBoundsSize(f.size) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Mac/View/SidebarSplitView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class SidebarSplitView: NSSplitView {} 4 | -------------------------------------------------------------------------------- /Mac/View/SidebarTableRowView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class SidebarTableRowView: NSTableRowView { 4 | override var isEmphasized: Bool { 5 | set {} 6 | get { 7 | false 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Mac/View/StorageView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import Foundation 3 | 4 | class StorageView: NSView { 5 | override func draw(_ dirtyRect: NSRect) { 6 | super.draw(dirtyRect) 7 | layer?.backgroundColor = NSColor(red: 0.96, green: 0.96, blue: 0.96, alpha: 1.0).cgColor 8 | } 9 | 10 | override func awakeFromNib() { 11 | var f = frame 12 | f.size.width = 138 13 | frame = f 14 | setFrameSize(f.size) 15 | setBoundsSize(f.size) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Mac/View/TitleBarView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class TitleBarView: NSView { 4 | var onMouseEnteredClosure: (()->())? 5 | var onMouseExitedClosure: (()->())? 6 | 7 | override func awakeFromNib() { 8 | addTrackingArea(NSTrackingArea(rect: bounds, options: [.activeAlways, .mouseEnteredAndExited], owner: self, userInfo: nil)) 9 | } 10 | 11 | override func layout() { 12 | super.layout() 13 | trackingAreas.forEach { [weak self] area in 14 | self?.removeTrackingArea(area) 15 | } 16 | addTrackingArea(NSTrackingArea(rect: bounds, options: [.activeAlways, .mouseEnteredAndExited], owner: self, userInfo: nil)) 17 | } 18 | 19 | override func mouseEntered(with event: NSEvent) { 20 | onMouseEnteredClosure?() 21 | } 22 | 23 | override func mouseExited(with event: NSEvent) { 24 | onMouseExitedClosure?() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Mac/View/TitleTextField.swift: -------------------------------------------------------------------------------- 1 | import Carbon.HIToolbox 2 | import Cocoa 3 | 4 | class TitleTextField: NSTextField { 5 | public var vcDelegate: ViewController! 6 | public var restoreResponder: NSResponder? 7 | 8 | override func performKeyEquivalent(with event: NSEvent) -> Bool { 9 | let pasteboard = NSPasteboard.general 10 | 11 | if event.modifierFlags.contains(.command), 12 | event.keyCode == kVK_ANSI_C, 13 | let selectedRange = currentEditor()?.selectedRange, 14 | selectedRange.length > 0 15 | { 16 | // Processing copy commands 17 | let selectedString = (stringValue as NSString).substring(with: selectedRange) 18 | pasteboard.declareTypes([NSPasteboard.PasteboardType.string], owner: nil) 19 | pasteboard.setString(selectedString, forType: NSPasteboard.PasteboardType.string) 20 | } 21 | 22 | // Checks if Command + V was pressed and the current NSTextField is the first responder. 23 | if event.modifierFlags.contains(.command), 24 | event.keyCode == kVK_ANSI_V, 25 | window?.firstResponder == currentEditor() 26 | { 27 | if let items = pasteboard.pasteboardItems { 28 | for item in items { 29 | if let string = item.string(forType: .string) { 30 | let noNewlineString = string.replacingOccurrences(of: "\n", with: " ") 31 | pasteboard.clearContents() 32 | pasteboard.setString(noNewlineString, forType: .string) 33 | } 34 | } 35 | } 36 | } 37 | 38 | return super.performKeyEquivalent(with: event) 39 | } 40 | 41 | override func becomeFirstResponder() -> Bool { 42 | if let note = EditTextView.note { 43 | stringValue = note.getShortTitle() 44 | } 45 | return super.becomeFirstResponder() 46 | } 47 | 48 | override func textDidEndEditing(_ notification: Notification) { 49 | saveTitle() 50 | } 51 | 52 | public func saveTitle() { 53 | guard stringValue.count > 0, let vc = ViewController.shared(), let note = EditTextView.note else { return } 54 | 55 | let currentTitle = stringValue.trimmingCharacters(in: NSCharacterSet.newlines) 56 | let currentName = note.getFileName() 57 | 58 | defer { 59 | updateNotesTableView() 60 | } 61 | 62 | if currentName != currentTitle { 63 | let ext = note.url.pathExtension 64 | let fileName = 65 | currentTitle 66 | .trimmingCharacters(in: CharacterSet.whitespaces) 67 | .replacingOccurrences(of: ":", with: "-") 68 | .replacingOccurrences(of: "/", with: ":") 69 | let dst = note.project.url.appendingPathComponent(fileName).appendingPathExtension(ext) 70 | 71 | if !FileManager.default.fileExists(atPath: dst.path), note.move(to: dst) { 72 | vc.updateTitle(newTitle: currentTitle) 73 | updateNotesTableView() 74 | } else { 75 | vc.updateTitle(newTitle: currentTitle) 76 | resignFirstResponder() 77 | updateNotesTableView() 78 | let alert = NSAlert() 79 | alert.alertStyle = .informational 80 | alert.informativeText = String(format: NSLocalizedString("This %@ under this folder already exists!", comment: ""), currentTitle) 81 | alert.messageText = NSLocalizedString("Please change the title", comment: "") 82 | alert.runModal() 83 | } 84 | } else { 85 | vc.updateTitle(newTitle: currentTitle) 86 | resignFirstResponder() 87 | updateNotesTableView() 88 | } 89 | } 90 | 91 | public func hasFocus() -> Bool { 92 | var inFocus = false 93 | inFocus = (window?.firstResponder is NSTextView) && window?.fieldEditor(false, for: nil) != nil && isEqual(to: (window?.firstResponder as? NSTextView)?.delegate) 94 | return inFocus 95 | } 96 | 97 | public func editModeOn() { 98 | MainWindowController.shared()?.makeFirstResponder(self) 99 | } 100 | 101 | public func updateNotesTableView() { 102 | guard let vc = ViewController.shared(), let note = EditTextView.note else { return } 103 | vc.notesTableView.reloadRow(note: note) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Mac/en.lproj/ContentViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSTextFieldCell"; title = "Update Time:"; ObjectID = "GN1-aA-6kJ"; */ 3 | "GN1-aA-6kJ.title" = "Update Time:"; 4 | 5 | /* Class = "NSTextFieldCell"; title = "Word Count:"; ObjectID = "ITG-Xm-ygA"; */ 6 | "ITG-Xm-ygA.title" = "Word Count:"; 7 | 8 | /* Class = "NSTextFieldCell"; title = "Create Time:"; ObjectID = "LDe-hw-GD1"; */ 9 | "LDe-hw-GD1.title" = "Create Time:"; -------------------------------------------------------------------------------- /Mac/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | MiaoYan 4 | Created by Tw93 on 2022/6/9. 5 | */ 6 | 7 | 8 | "CFBundleDisplayName" = "MiaoYan"; 9 | 10 | "CFBundleName" = "MiaoYan"; 11 | -------------------------------------------------------------------------------- /Mac/ja.lproj/ContentViewController.strings: -------------------------------------------------------------------------------- 1 | /* Class = "NSTextFieldCell"; title = "Update Time:"; ObjectID = "GN1-aA-6kJ"; */ 2 | "GN1-aA-6kJ.title" = "更新時間:"; 3 | 4 | /* Class = "NSTextFieldCell"; title = "Word Count:"; ObjectID = "ITG-Xm-ygA"; */ 5 | "ITG-Xm-ygA.title" = "文字数:"; 6 | 7 | /* Class = "NSTextFieldCell"; title = "Create Time:"; ObjectID = "LDe-hw-GD1"; */ 8 | "LDe-hw-GD1.title" = "作成日時:"; 9 | 10 | /* Class = "NSTextFieldCell"; title = "wordCount"; ObjectID = "Muy-lO-8hh"; */ 11 | "Muy-lO-8hh.title" = "文字数カウント"; 12 | 13 | /* Class = "NSTextFieldCell"; title = "updateTime"; ObjectID = "yP9-xN-9tv"; */ 14 | "yP9-xN-9tv.title" = "更新時間"; 15 | -------------------------------------------------------------------------------- /Mac/ja.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | MiaoYan 4 | Created by Tw93 on 2022/6/9. 5 | Edit by ganwt on 2023/2/13 6 | */ 7 | 8 | 9 | "CFBundleDisplayName" = "妙言"; 10 | 11 | "CFBundleName" = "妙言"; 12 | -------------------------------------------------------------------------------- /Mac/zh-Hans.lproj/ContentViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSTextFieldCell"; title = "Update Time:"; ObjectID = "GN1-aA-6kJ"; */ 3 | "GN1-aA-6kJ.title" = "更新时间:"; 4 | 5 | /* Class = "NSTextFieldCell"; title = "Word Count:"; ObjectID = "ITG-Xm-ygA"; */ 6 | "ITG-Xm-ygA.title" = "字数统计:"; 7 | 8 | /* Class = "NSTextFieldCell"; title = "Create Time:"; ObjectID = "LDe-hw-GD1"; */ 9 | "LDe-hw-GD1.title" = "创建时间:"; 10 | 11 | -------------------------------------------------------------------------------- /Mac/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | MiaoYan 4 | Created by Tw93 on 2022/6/9. 5 | */ 6 | 7 | 8 | "CFBundleDisplayName" = "妙言"; 9 | 10 | "CFBundleName" = "妙言"; 11 | -------------------------------------------------------------------------------- /Mac/zh-Hant.lproj/ContentViewController.strings: -------------------------------------------------------------------------------- 1 | /* Class = "NSTextFieldCell"; title = "createTime"; ObjectID = "E1l-5G-VcH"; */ 2 | "E1l-5G-VcH.title" = "createTime"; 3 | 4 | /* Class = "NSTextFieldCell"; title = "Update Time:"; ObjectID = "GN1-aA-6kJ"; */ 5 | "GN1-aA-6kJ.title" = "更新時間:"; 6 | 7 | /* Class = "NSTextFieldCell"; title = "Word Count:"; ObjectID = "ITG-Xm-ygA"; */ 8 | "ITG-Xm-ygA.title" = "字數統計:"; 9 | 10 | /* Class = "NSTextFieldCell"; title = "Create Time:"; ObjectID = "LDe-hw-GD1"; */ 11 | "LDe-hw-GD1.title" = "建立時間:"; 12 | 13 | /* Class = "NSTextFieldCell"; title = "wordCount"; ObjectID = "Muy-lO-8hh"; */ 14 | "Muy-lO-8hh.title" = "wordCount"; 15 | 16 | /* Class = "NSTextFieldCell"; title = "updateTime"; ObjectID = "yP9-xN-9tv"; */ 17 | "yP9-xN-9tv.title" = "updateTime"; 18 | 19 | -------------------------------------------------------------------------------- /Mac/zh-Hant.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* InfoPlist.strings 2 | MiaoYan 3 | Created by Tw93 on 2022/6/9. */ 4 | "CFBundleDisplayName" = "妙言"; 5 | 6 | /* Bundle name */ 7 | "CFBundleName" = "妙言"; 8 | 9 | /* (No Comment) */ 10 | "Markdwon" = "Markdown"; 11 | 12 | -------------------------------------------------------------------------------- /Mac/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "%d MiaoYan" = "%d 篇妙言"; 3 | 4 | /* No comment provided by engineer. */ 5 | "🍭 Found that there are multiple titles of this~" = "🍭 這裡有好幾個標題,使用搜尋能力~"; 6 | 7 | /* No comment provided by engineer. */ 8 | "🍭 Image upload in progress~" = "🍭 正在上傳圖片~"; 9 | 10 | /* No comment provided by engineer. */ 11 | "🎉 Automatic typesetting succeeded~" = "🎉 自動排版完成~"; 12 | 13 | /* No comment provided by engineer. */ 14 | "🎉 Saved to Downloads folder~" = "🎉 已經輸出到下載目錄~"; 15 | 16 | /* No comment provided by engineer. */ 17 | "🎉 URL is successfully copied, Use it anywhere~" = "🎉 成功複製外部連結,可以在其他地方使用了~"; 18 | 19 | /* No comment provided by engineer. */ 20 | "😶‍🌫 Format is only possible after exiting preview mode~" = "😶‍🌫 文件排版得結束預覽模式才能使用~"; 21 | 22 | /* No comment provided by engineer. */ 23 | "😶‍🌫 Image upload failed, Use local~" = "😶‍🌫 圖片上傳失敗,改放在本機端,可手動上傳~"; 24 | 25 | /* No comment provided by engineer. */ 26 | "😶‍🌫 No delimiter --- identification, Cannot use MiaoYan PPT~" = "😶‍🌫 沒有分隔符號 --- 標示,無法使用妙言 PPT~"; 27 | 28 | /* No comment provided by engineer. */ 29 | "😶‍🌫 Please make sure your title exists~" = "😶‍🌫 請確定填寫的標題是否存在~"; 30 | 31 | /* No comment provided by engineer. */ 32 | "😶‍🌫 The current Mac system does not support export, please upgrade to above 11.0~" = "😶‍🌫 目前的 Mac 系統不支援輸出功能,請升級到 11.0 版以上~"; 33 | 34 | /* No comment provided by engineer. */ 35 | "🙊 In single open mode, Exit with Command+Shift+W ~" = "🙊 在單獨編輯器模式中,可以敲下 Command+Shift+W 結束~"; 36 | 37 | /* No comment provided by engineer. */ 38 | "🙊 Please make sure your Mac is installed %@ ~" = "🙊 請確定你的 Mac 已經安裝並設定好 %@~"; 39 | 40 | /* No comment provided by engineer. */ 41 | "🙊 Press ESC key to exit~" = "🙊 按 ESC 鍵結束簡報模式~"; 42 | 43 | /* No comment provided by engineer. */ 44 | "🙊 Starting export~" = "🙊 開始匯出~"; 45 | 46 | /* No comment provided by engineer. */ 47 | "Add" = "加入"; 48 | 49 | /* No comment provided by engineer. */ 50 | "Are you sure you want to irretrievably delete %d note(s)?" = "您確定要永久刪除 %d 篇筆記嗎?"; 51 | 52 | /* No comment provided by engineer. */ 53 | "Are you sure you want to remove project \"%@\" and all files inside?" = "您確定要刪除「%@」專案及其所有檔案嗎?"; 54 | 55 | /* Delete menu */ 56 | "Cancel" = "取消"; 57 | 58 | /* No comment provided by engineer. */ 59 | "Confirm" = "確認"; 60 | 61 | /* No comment provided by engineer. */ 62 | "Copy HTML" = "拷貝 HTML"; 63 | 64 | /* No comment provided by engineer. */ 65 | "Copy Link" = "拷貝連結"; 66 | 67 | /* No comment provided by engineer. */ 68 | "Copy Plain Text" = "拷貝純文字"; 69 | 70 | /* No comment provided by engineer. */ 71 | "Delete" = "刪除"; 72 | 73 | /* No comment provided by engineer. */ 74 | "Delete Folder" = "刪除檔案夾"; 75 | 76 | /* No comment provided by engineer. */ 77 | "Edit Link…" = "編輯連結…"; 78 | 79 | /* Sidebar label */ 80 | "MiaoYan" = "妙言"; 81 | 82 | /* Menu */ 83 | "Move" = "移動"; 84 | 85 | /* Menu */ 86 | "Export" = "導出"; 87 | 88 | /* No comment provided by engineer. */ 89 | "New project" = "建立專案"; 90 | 91 | /* No comment provided by engineer. */ 92 | "Please change the title" = "請更改標題"; 93 | 94 | /* No comment provided by engineer. */ 95 | "Preferences" = "偏好設定"; 96 | 97 | /* Delete menu */ 98 | "Remove" = "移除"; 99 | 100 | /* No comment provided by engineer. */ 101 | "Remove Link" = "移除連結"; 102 | 103 | /* No comment provided by engineer. */ 104 | "Remove note(s)" = "移除筆記"; 105 | 106 | /* No comment provided by engineer. */ 107 | "Rename Folder" = "重新命名檔案夾"; 108 | 109 | /* No comment provided by engineer. */ 110 | "Restart to MiaoYan to take effect" = "重新啟動妙言即可生效"; 111 | 112 | /* No comment provided by engineer. */ 113 | "Show in Finder" = "在 Finder 中顯示"; 114 | 115 | /* View menu */ 116 | "Sort by" = "排序方式"; 117 | 118 | /* No comment provided by engineer. */ 119 | "This %@ under this folder already exists!" = "此檔案夾下的 $@ 已經存在!"; 120 | 121 | /* Delete menu */ 122 | "This action cannot be undone." = "本動作不可復原。"; 123 | 124 | /* Sidebar label */ 125 | "Trash" = "垃圾桶"; 126 | 127 | /* Untitled Note */ 128 | "Untitled Note" = "未命名筆記"; 129 | 130 | /* Menu */ 131 | "View" = "顯示方式"; 132 | 133 | -------------------------------------------------------------------------------- /MiaoYan.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MiaoYan.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MiaoYan.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MiaoYan.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /MiaoYan.xcodeproj/xcshareddata/xcschemes/MiaoYan.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 55 | 56 | 57 | 63 | 64 | 66 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /MiaoYan.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MiaoYan.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | MAC_TARGET_VERSION = '10.15' 4 | 5 | def mac_pods 6 | pod 'Sparkle' 7 | pod 'AppCenter' 8 | pod 'Alamofire' 9 | pod 'SwiftyJSON' 10 | pod 'Highlightr' 11 | pod 'libcmark_gfm' 12 | pod 'SSZipArchive' 13 | pod 'SwiftLint' 14 | pod 'MASShortcut' 15 | end 16 | 17 | 18 | target 'MiaoYan' do 19 | platform :osx, MAC_TARGET_VERSION 20 | mac_pods 21 | end 22 | 23 | post_install do |installer| 24 | xcode_base_version = `xcodebuild -version | grep 'Xcode' | awk '{print $2}' | cut -d . -f 1` 25 | installer.pods_project.targets.each do |project| 26 | project.build_configurations.each do |config| 27 | config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.15' 28 | config.build_settings['SWIFT_VERSION'] = '5.0' 29 | config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES' 30 | config.build_settings['DEAD_CODE_STRIPPING'] = 'YES' 31 | config.build_settings['ENABLE_MODULE_VERIFIER'] = 'NO' 32 | config.build_settings['STRIP_INSTALLED_PRODUCT'] = 'YES' 33 | # config.build_settings['STRIP_STYLE'] = 'all' 34 | config.build_settings['STRIP_SWIFT_SYMBOLS'] = 'YES' 35 | config.build_settings['COPY_PHASE_STRIP'] = 'NO' 36 | config.build_settings.delete('ARCHS') 37 | if config.base_configuration_reference && Integer(xcode_base_version) >= 15 38 | xcconfig_path = config.base_configuration_reference.real_path 39 | xcconfig = File.read(xcconfig_path) 40 | xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR") 41 | File.open(xcconfig_path, "w") { |file| file << xcconfig_mod } 42 | end 43 | end 44 | 45 | if project.name == 'cmark-gfm-swift-macOS' 46 | source_files = project.source_build_phase.files 47 | dummy = source_files.find do |file| 48 | file.file_ref.name == 'scanners.re' 49 | end 50 | source_files.delete dummy 51 | 52 | dummyM = source_files.find do |file| 53 | file.file_ref.name == 'module.modulemap' 54 | end 55 | source_files.delete dummyM 56 | puts "Deleting source file #{dummy.inspect} from target #{target.inspect}." 57 | end 58 | end 59 | end 60 | 61 | install! 'cocoapods', :deterministic_uuids => false 62 | 63 | # ignore all warnings from all pods 64 | inhibit_all_warnings! 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

English | 中文 | 日本語

2 | 3 |

4 | 5 |

妙言

6 |
7 | 8 | twitter 9 | 10 | telegram 11 | 12 | GitHub downloads 13 | 14 | GitHub commit 15 | 16 | GitHub closed issues 17 | Minimum supported version 18 |
19 |
轻灵的 Markdown 笔记本伴你写出妙言~
20 |

21 | 22 | 23 | 24 | ## 特点 25 | 26 | - 🏂 **妙**:纯本地使用、安全、语法高亮、黑暗模式、源文件保存、国际化、演示模式、[PPT 模式](#妙言-ppt)、单独编辑模式、文档自动排版、文档导出、内部跳转、图床、LaTeX、Mermaid、PlantUML、Markmap 脑图 27 | - 🎊 **美**:极简的设计风格,文件夹 + 文件列表 + 编辑器方式 3 列模式 28 | - 🚄 **快**:使用 Swift5 原生开发,相比 Web 套壳方式性能体验好 29 | - 🥛 **简**:很轻巧,纯编辑器输入体验,众多快捷键助你快人一步 30 | 31 | ## 首次使用 32 | 33 | 1. 从 GitHub Releases 中 **下载** 最新的 dmg 安装包,macOS Big Sur 及以上版本体验更好,如安装出现问题请参考 [文档](https://zhuanlan.zhihu.com/p/135948430),此外也支持从 `brew install miaoyan --cask` 安装。 34 | 2. 可以在 iCloud 或根目录下创建一个 `MiaoYan` 的文件夹,打开妙言的设置,将默认存储地址修改成这个。 35 | 3. 点击妙言左上角新增文件夹的图标,创建好自己的文档分类文件夹,就可以开始使用了。 36 | 4. 同样假如你不习惯默认的字体,可以在设置中修改成其他的正常字体。 37 | 38 | ## 快捷键 39 | 40 | #### 窗口操作 41 | 42 | - `command + 1`:收起展开目录 43 | - `command + 2`:收起展开文档列表 44 | - `command + 3`:切换编辑和预览 45 | - `command + 4`:切换到演示模式 46 | - `command + option + m`:全局唤起/隐藏妙言 47 | 48 | #### 文档操作 49 | 50 | - `command + n`:新建文档 51 | - `command + r`:重命名文档 52 | - `command + d`:复制文档 53 | - `command + o`:单独打开文档 54 | - `command + delete`:删除文档 55 | - `command + shift + n`:新建文件夹 56 | - `command + shift + l`:自动排版 57 | - `command + option + r`:在 Finder 中显示 58 | - `command + option + i`:显示字数等文档属性 59 | - `command + option + p`:启动妙言 PPT 预览 60 | 61 | 🏂 此外还有很多快捷键 👆🏻 👇🏻 👈🏻 👉🏻 等着爱折腾的你去寻找~ 62 | 63 | ## 妙言 PPT 64 | 65 | 66 | 67 | 1. 新朋友默认初始化会生成模版,如果是老朋友需升级到 1.0,可以 Copy [此文件](https://raw.githubusercontent.com/tw93/MiaoYan/master/Resources/Initial/%E5%A6%99%E8%A8%80%20PPT.md) 到妙言玩一玩。 68 | 2. 执行 `command + option + p` 可以启动妙言 PPT 预览,也可以选中文档点击右键,选择 `妙言 PPT` 打开。 69 | 3. 只有在有 `---` 分隔符标志的文档中,才可启用 PPT 模式,演示过程中你可以 `回车键` 预览演讲大纲,`ESC` 键可退出 PPT 模式。 70 | 4. 你可以使用 HTML 来自定义效果,更多复杂的用法可以参考 [Reveal](https://revealjs.com/markdown/) 文档。 71 | 72 | ## 为什么要做妙言 73 | 74 | - 之前有尝试过众多的笔记应用,大学时期为知笔记、印象笔记,工作时候用过 Ulysses、Quiver、MWeb、Bear、Typora,种种原因,没有找到一个习惯的 Markdown 应用,才有了做妙言的想法。 75 | - 本职为前端开发,会一点 iOS 开发,爱折腾,借妙言来玩一下 Swift 以及独立产品,当做一个很愉快的事情。 76 | - 更多介绍可见 [妙言 - 更适合工程师用的笔记应用](https://tw93.fun/2022-09-09/miaoyan.html),很欢迎交流和建议 77 | 78 | ## 支持 79 | 80 | - 我有两只猫,假如觉得妙言让你生活更美好,可以给猫 喂罐头 🥩🍤。 81 | - 如果你喜欢妙言,可以在 Github Star,更欢迎 [推荐](https://twitter.com/intent/tweet?text=%23%E5%A6%99%E8%A8%80%20-%20%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E5%A5%BD%E7%9C%8B%E7%9A%84%E5%BC%80%E6%BA%90%E7%9A%84%20Mac%20%20Markdown%20%E7%BC%96%E8%BE%91%E5%99%A8%EF%BC%8C%E6%B2%A1%E6%9C%89%E4%BB%BB%E4%BD%95%E5%A4%9A%E4%BD%99%E7%9A%84%E5%8A%9F%E8%83%BD%EF%BC%8C%E4%BD%BF%E7%94%A8%E5%8E%9F%E7%94%9F%20Swift%20%E5%BC%80%E5%8F%91%EF%BC%8C%E8%BD%BB%E9%87%8F%E6%80%A7%E8%83%BD%E9%AB%98%EF%BC%8C%E5%AE%89%E5%85%A8%E7%BA%AF%E6%9C%AC%E5%9C%B0%E4%BD%BF%E7%94%A8%EF%BC%8C%E5%85%B7%E5%A4%87%E8%AF%AD%E6%B3%95%E9%AB%98%E4%BA%AE%E3%80%81%E9%BB%91%E6%9A%97%E6%A8%A1%E5%BC%8F%E3%80%81%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96%E3%80%81%E5%8D%95%E7%8B%AC%E7%BC%96%E8%BE%91%E3%80%81%E6%BC%94%E7%A4%BA%E6%A8%A1%E5%BC%8F%E3%80%81%E5%9B%BE%E5%BA%8A%E7%AD%89%E5%8A%9F%E8%83%BD%E3%80%82&url=https://github.com/tw93/MiaoYan) 给你志同道合的朋友使用。 82 | - 可以关注我的 [Twitter](https://twitter.com/HiTw93) 获取到最新的妙言更新消息,也欢迎加入 [Telegram](https://t.me/+GclQS9ZnxyI2ODQ1) 聊天群。 83 | 84 | ## 感谢 85 | 86 | - KristopherGBaker/libcmark_gfm:适用于 cmark-gfm 的 Swift 兼容框架 87 | - raspu/Highlightr:语法高亮能力 88 | - glushchenko/fsnotes:妙言部分初始化代码来源于此项目,很感谢作者 89 | - lxgw/LxgwWenKai:一款漂亮的开源中文字体,妙言将其作为默认字体 90 | - michaelhenry/Prettier.swift:妙言格式化能力升级成 Prettier 的思路来源 91 | - hakimel/reveal.js:妙言 PPT 底层渲染依赖此框架 92 | - 感谢 Vercel 给妙言 [官网](https://miaoyan.app/) 提供静态渲染能力 93 | 96 | 97 | # 协议 98 | 99 | - 遵循 MIT 协议 100 | - 请自由地享受和参与开源 101 | -------------------------------------------------------------------------------- /Resources/DownView.bundle/js/auto-render.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={771:function(t){t.exports=e}},r={};function n(e){var a=r[e];if(void 0!==a)return a.exports;var i=r[e]={exports:{}};return t[e](i,i.exports,n),i.exports}n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var a={};return function(){n.d(a,{default:function(){return s}});var e=n(771),t=n.n(e),r=function(e,t,r){for(var n=r,a=0,i=e.length;n0&&(a.push({type:"text",data:e.slice(0,n)}),e=e.slice(n));var l=t.findIndex((function(t){return e.startsWith(t.left)}));if(-1===(n=r(t[l].right,e,t[l].left.length)))break;var d=e.slice(0,n+t[l].right.length),s=i.test(d)?d:e.slice(t[l].left.length,n);a.push({type:"math",data:s,rawData:d,display:t[l].display}),e=e.slice(n+t[l].right.length)}return""!==e&&a.push({type:"text",data:e}),a},l=function(e,r){var n=o(e,r.delimiters);if(1===n.length&&"text"===n[0].type)return null;for(var a=document.createDocumentFragment(),i=0;i { 39 | el[key] = value 40 | }) 41 | } 42 | if (attrs) { 43 | Object.entries(attrs).forEach(([key, value]) => { 44 | el.setAttribute(key, value) 45 | }) 46 | } 47 | return el 48 | } 49 | 50 | const memoizedPreloadJS = memoize(url => { 51 | document.head.append(createElement('link', { rel: 'preload', as: 'script', href: url })) 52 | }); 53 | 54 | function loadJSItem(item, context) { 55 | if (item.type === 'script') { 56 | return new Promise((resolve, reject) => { 57 | var _item$data; 58 | document.head.append(createElement('script', _extends({}, item.data, { onload: resolve, onerror: reject }))); 59 | if (!((_item$data = item.data) != null && _item$data.src)) resolve() 60 | }) 61 | } 62 | if (item.type === 'iife') { 63 | const { fn, getParams } = item.data; 64 | fn(...((getParams == null ? void 0 : getParams(context)) || [])) 65 | } 66 | } 67 | 68 | function loadCSSItem(item) { 69 | if (item.type === 'style') { 70 | document.head.append(createElement('style', { textContent: item.data })) 71 | } else if (item.type === 'stylesheet') { 72 | document.head.append(createElement('link', _extends({ rel: 'stylesheet' }, item.data))) 73 | } 74 | } 75 | 76 | async function loadJS(items, context) { 77 | const needPreload = items.filter(item => { 78 | var _item$data2; 79 | return item.type === 'script' && ((_item$data2 = item.data) == null ? void 0 : _item$data2.src) 80 | }); 81 | if (needPreload.length > 1) needPreload.forEach(item => memoizedPreloadJS(item.data.src)); 82 | context = _extends({ getMarkmap: () => window.markmap }, context); 83 | for (const item of items) { 84 | await loadJSItem(item, context) 85 | } 86 | } 87 | 88 | function loadCSS(items) { 89 | for (const item of items) { 90 | loadCSSItem(item) 91 | } 92 | } 93 | 94 | var _window$markmap, _window$markmap$autoL, _window$markmap2, _window$markmap2$auto; 95 | const enabled = {}; 96 | const ready = loadJS(((_window$markmap = window.markmap) == null ? void 0 : (_window$markmap$autoL = _window$markmap.autoLoader) == null ? void 0 : _window$markmap$autoL.baseJs) || [{ 97 | type: 'script', 98 | data: { src: 'js/d3.min.js' } 99 | }, { type: 'script', data: { src: 'js/markmap-view.min.js' } }]).then(() => { 100 | var _markmap$autoLoader; 101 | const { markmap } = window; 102 | loadCSS([{ type: 'style', data: markmap.globalCSS }]); 103 | (_markmap$autoLoader = markmap.autoLoader) == null ? void 0 : _markmap$autoLoader.onReady == null ? void 0 : _markmap$autoLoader.onReady() 104 | }); 105 | 106 | function transform(transformer, content) { 107 | const { root, features } = transformer.transform(content); 108 | const keys = Object.keys(features).filter(key => !enabled[key]); 109 | keys.forEach(key => { 110 | enabled[key] = true 111 | }); 112 | const { styles, scripts } = transformer.getAssets(keys); 113 | const { markmap } = window; 114 | if (styles) markmap.loadCSS(styles); 115 | if (scripts) markmap.loadJS(scripts); 116 | return root 117 | } 118 | 119 | function render(el) { 120 | const { Transformer, Markmap, autoLoader } = window.markmap; 121 | const lines = el.textContent.split('\n'); 122 | let indent = Infinity; 123 | lines.forEach(line => { 124 | const spaces = line.match(/^\s*/)[0].length; 125 | if (spaces < line.length) indent = Math.min(indent, spaces) 126 | }); 127 | const content = lines.map(line => line.slice(indent)).join('\n'); 128 | const transformer = new Transformer(autoLoader == null ? void 0 : autoLoader.transformPlugins); 129 | el.innerHTML = ''; 130 | const svg = el.firstChild; 131 | const mm = Markmap.create(svg, { 132 | embedGlobalCSS: true, 133 | duration: 0, 134 | autoFit: true, 135 | paddingX: 6, 136 | fitRatio: 0.92, 137 | spacingVertical: 8, 138 | }); 139 | const doRender = () => { 140 | const root = transform(transformer, content); 141 | mm.setData(root); 142 | mm.fit() 143 | }; 144 | transformer.hooks.retransform.tap(doRender); 145 | doRender() 146 | } 147 | 148 | async function renderAllUnder(container) { 149 | await ready; 150 | container.querySelectorAll('.markmap').forEach(render) 151 | } 152 | 153 | function renderAll() { 154 | return renderAllUnder(document) 155 | } 156 | 157 | if (!((_window$markmap2 = window.markmap) != null && (_window$markmap2$auto = _window$markmap2.autoLoader) != null && _window$markmap2$auto.manual)) { 158 | if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => { 159 | renderAll() 160 | }); else renderAll() 161 | } 162 | exports.ready = ready; 163 | exports.render = render; 164 | exports.renderAll = renderAll; 165 | exports.renderAllUnder = renderAllUnder 166 | })(this.markmap.autoLoader = this.markmap.autoLoader || {}); 167 | -------------------------------------------------------------------------------- /Resources/DownView.bundle/ppt/dist/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v4.0 | 20180602 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, main, menu, nav, section { 29 | display: block; 30 | } 31 | -------------------------------------------------------------------------------- /Resources/DownView.bundle/ppt/dist/theme/night.css: -------------------------------------------------------------------------------- 1 | @import "night_var.css"; 2 | @import "common.css"; 3 | 4 | -------------------------------------------------------------------------------- /Resources/DownView.bundle/ppt/dist/theme/night_var.css: -------------------------------------------------------------------------------- 1 | section.has-light-background, 2 | section.has-light-background h1, 3 | section.has-light-background h2, 4 | section.has-light-background h3, 5 | section.has-light-background h4, 6 | section.has-light-background h5, 7 | section.has-light-background h6 { 8 | color: #262626; 9 | } 10 | 11 | /********************************************* 12 | * GLOBAL STYLES 13 | *********************************************/ 14 | :root { 15 | --r-main-color: #e8e8eb; 16 | --r-heading-color: #e8e8eb; 17 | --r-background-color: #21262b; 18 | --r-code-background-color:#282D32; 19 | --r-link-color: #1c9cf0; 20 | --r-link-color-dark: #1c9cf0; 21 | --r-link-color-hover: #f3d7ac; 22 | --r-selection-background-color: #e7ad52; 23 | --r-selection-color: #fff; 24 | --r-highlight-color: #05A699; 25 | } 26 | 27 | .hljs { 28 | color: #ABB2BF; 29 | background: #191F25; 30 | } 31 | 32 | .hljs-comment, .hljs-quote { 33 | color: #ABB2BF; 34 | } 35 | 36 | .hljs-doctag, .hljs-formula, .hljs-keyword { 37 | color: #9B79F7 38 | } 39 | 40 | .hljs-deletion, .hljs-name, .hljs-section, .hljs-selector-tag, .hljs-subst { 41 | color: #ED716C 42 | } 43 | 44 | .hljs-literal { 45 | color: #56b6c2 46 | } 47 | 48 | .hljs-addition, .hljs-attribute, .hljs-meta .hljs-string, .hljs-regexp, .hljs-string { 49 | color: #8FFCCD 50 | } 51 | 52 | .hljs-attr, .hljs-number, .hljs-selector-attr, .hljs-selector-class, .hljs-selector-pseudo, .hljs-template-variable, .hljs-type, .hljs-variable { 53 | color: #F7CC8F 54 | } 55 | 56 | .hljs-bullet, .hljs-link, .hljs-meta, .hljs-selector-id, .hljs-symbol, .hljs-title { 57 | color: #99E0FC 58 | } 59 | 60 | .hljs-built_in, .hljs-class .hljs-title, .hljs-title.class_ { 61 | color: #ED716C 62 | } 63 | 64 | .hljs-emphasis { 65 | font-style: italic 66 | } 67 | 68 | .hljs-strong { 69 | font-weight: 700 70 | } 71 | 72 | .hljs-link { 73 | text-decoration: underline 74 | } 75 | 76 | 77 | .reveal strong, 78 | .reveal b { 79 | color: #F7B900; 80 | } 81 | -------------------------------------------------------------------------------- /Resources/DownView.bundle/ppt/dist/theme/white.css: -------------------------------------------------------------------------------- 1 | @import "white_var.css"; 2 | @import "common.css"; 3 | 4 | -------------------------------------------------------------------------------- /Resources/DownView.bundle/ppt/dist/theme/white_var.css: -------------------------------------------------------------------------------- 1 | section.has-dark-background, 2 | section.has-dark-background h1, 3 | section.has-dark-background h2, 4 | section.has-dark-background h3, 5 | section.has-dark-background h4, 6 | section.has-dark-background h5, 7 | section.has-dark-background h6 { 8 | color: #fff; 9 | } 10 | 11 | /********************************************* 12 | * GLOBAL STYLES 13 | *********************************************/ 14 | :root { 15 | --r-main-color: #262626; 16 | --r-background-color: #fff; 17 | --r-heading-color: #262626; 18 | --r-link-color: #1C9CF0; 19 | --r-code-background-color:#F7F7F7; 20 | --r-link-color-dark: #1C9CF0; 21 | --r-link-color-hover: #6ca0e8; 22 | --r-selection-background-color: #98bdef; 23 | --r-selection-color: #fff; 24 | --r-highlight-color: #05A699; 25 | } 26 | 27 | 28 | .hljs { 29 | background: #f8f8f8; 30 | color: #000; 31 | } 32 | 33 | .hljs-comment, 34 | .hljs-quote, 35 | .hljs-variable { 36 | color: #035c21; 37 | } 38 | 39 | .hljs-built_in, 40 | .hljs-keyword, 41 | .hljs-name, 42 | .hljs-selector-tag, 43 | .hljs-tag { 44 | color: #0a69d9; 45 | } 46 | 47 | .hljs-addition, 48 | .hljs-attribute, 49 | .hljs-literal, 50 | .hljs-section, 51 | .hljs-string, 52 | .hljs-template-tag, 53 | .hljs-template-variable, 54 | .hljs-title, 55 | .hljs-type { 56 | color: #bf3889; 57 | } 58 | 59 | .hljs-deletion, 60 | .hljs-meta, 61 | .hljs-selector-attr, 62 | .hljs-selector-pseudo { 63 | color: #208bff; 64 | } 65 | 66 | .hljs-doctag { 67 | color: #6e7781; 68 | } 69 | 70 | .hljs-attr { 71 | color: #cf212e; 72 | } 73 | 74 | .hljs-bullet, 75 | .hljs-link, 76 | .hljs-symbol { 77 | color: #8250df; 78 | } 79 | 80 | .hljs-emphasis { 81 | font-style: italic; 82 | } 83 | 84 | .hljs-strong { 85 | font-weight: bold; 86 | -webkit-font-smoothing: antialiased; 87 | } 88 | 89 | .reveal strong, 90 | .reveal b { 91 | color: #ED220C; 92 | } 93 | -------------------------------------------------------------------------------- /Resources/DownView.bundle/ppt/plugin/math/math.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).RevealMath=e()}(this,(function(){"use strict";return Plugin=Object.assign(()=>{},{KaTeX:()=>{let t,e={version:"latest",delimiters:[{left:"$$",right:"$$",display:!0},{left:"$",right:"$",display:!1},{left:"\\(",right:"\\)",display:!1},{left:"\\[",right:"\\]",display:!0}],ignoredTags:["script","noscript","style","textarea","pre"]};const n=t=>new Promise(((e,n)=>{const a=document.createElement("script");a.type="text/javascript",a.onload=e,a.onerror=n,a.src=t,document.head.append(a)}));return{id:"katex",init:function(a){t=a;let i=t.getConfig().katex||{},s={...e,...i};const{local:o,version:l,extensions:r,...c}=s;let p="css/katex.min.css",x="js/auto-render.min.js",m=["js/katex.min.js"];m.push(x);const f=()=>{renderMathInElement(a.getSlidesElement(),c),t.layout()};(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href=t,document.head.appendChild(e)})(p),async function(t){for(const e of t)await n(e)}(m).then((()=>{t.isReady()?f():t.on("ready",f.bind(this))}))}}}})})); -------------------------------------------------------------------------------- /Resources/DownView.bundle/ppt/plugin/zoom/zoom.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).RevealZoom=t()}(this,(function(){"use strict"; 2 | /*! 3 | * reveal.js Zoom plugin 4 | */var e={id:"zoom",init:function(e){e.getRevealElement().addEventListener("mousedown",(function(o){var n=/Linux/.test(window.navigator.platform)?"ctrl":"alt",i=(e.getConfig().zoomKey?e.getConfig().zoomKey:n)+"Key",d=e.getConfig().zoomLevel?e.getConfig().zoomLevel:2;o[i]&&!e.isOverview()&&(o.preventDefault(),t.to({x:o.clientX,y:o.clientY,scale:d,pan:!1}))}))},destroy:function(){t.reset()}},t=function(){var e=1,o=0,n=0,i=-1,d=-1,l="transform"in document.body.style;function s(t,o){var n=r();if(t.width=t.width||1,t.height=t.height||1,t.x-=(window.innerWidth-t.width*o)/2,t.y-=(window.innerHeight-t.height*o)/2,l)if(1===o)document.body.style.transform="";else{var i=n.x+"px "+n.y+"px",d="translate("+-t.x+"px,"+-t.y+"px) scale("+o+")";document.body.style.transformOrigin=i,document.body.style.transform=d}else 1===o?(document.body.style.position="",document.body.style.left="",document.body.style.top="",document.body.style.width="",document.body.style.height="",document.body.style.zoom=""):(document.body.style.position="relative",document.body.style.left=-(n.x+t.x)/o+"px",document.body.style.top=-(n.y+t.y)/o+"px",document.body.style.width=100*o+"%",document.body.style.height=100*o+"%",document.body.style.zoom=o);e=o,document.documentElement.classList&&(1!==e?document.documentElement.classList.add("zoomed"):document.documentElement.classList.remove("zoomed"))}function c(){var t=.12*window.innerWidth,i=.12*window.innerHeight,d=r();nwindow.innerHeight-i&&window.scroll(d.x,d.y+(1-(window.innerHeight-n)/i)*(14/e)),owindow.innerWidth-t&&window.scroll(d.x+(1-(window.innerWidth-o)/t)*(14/e),d.y)}function r(){return{x:void 0!==window.scrollX?window.scrollX:window.pageXOffset,y:void 0!==window.scrollY?window.scrollY:window.pageYOffset}}return l&&(document.body.style.transition="transform 0.8s ease"),document.addEventListener("keyup",(function(o){1!==e&&27===o.keyCode&&t.out()})),document.addEventListener("mousemove",(function(t){1!==e&&(o=t.clientX,n=t.clientY)})),{to:function(o){if(1!==e)t.out();else{if(o.x=o.x||0,o.y=o.y||0,o.element){var n=o.element.getBoundingClientRect();o.x=n.left-20,o.y=n.top-20,o.width=n.width+40,o.height=n.height+40}void 0!==o.width&&void 0!==o.height&&(o.scale=Math.max(Math.min(window.innerWidth/o.width,window.innerHeight/o.height),1)),o.scale>1&&(o.x*=o.scale,o.y*=o.scale,s(o,o.scale),!1!==o.pan&&(i=setTimeout((function(){d=setInterval(c,1e3/60)}),800)))}},out:function(){clearTimeout(i),clearInterval(d),s({x:0,y:0},1),e=1},magnify:function(e){this.to(e)},reset:function(){this.out()},zoomLevel:function(){return e}}}();return function(){return e}})); 5 | -------------------------------------------------------------------------------- /Resources/Initial/Introduction to MiaoYan.md: -------------------------------------------------------------------------------- 1 |

2 |

3 |

MiaoYan

4 |
Lightweight Markdown app to help you write great sentences.
5 |
中文 | English
6 |

7 | 8 | ## Features 9 | 10 | - 🏂 **Fantastic**: Local use, security, syntax highlighting, dark mode, source file saving, international, presentation mode, PPT Mode, single edit mode, export file, internal jump, document auto typesetting, picture upload, LaTeX, Mermaid, PlantUML. 11 | - 🎊 **Beauty**: Minimalist design style, folder + file list + editor 3 column mode. 12 | - 🚄 **Fast**: Using Swift5 native development, the performance experience is much better compared to the Web. 13 | - 🥛 **Simple**: Very light, pure editor input experience, many shortcut keys to help you fast. 14 | 15 | ## First Use 16 | 17 | 1. Download the latest `MiaoYan.dmg` installation package from GitHub Releases and double-click to install it. 18 | 2. You can create a `MiaoYan` folder in iCloud or the root directory, open MiaoYan's Settings, and change the default storage address to this. 19 | 3. Click icon of the new folder in the upper left corner of MiaoYan, create your own document category folder, and you can start using it. 20 | 4. Similarly, if you are not used to the default font, you can change it to other normal fonts in the settings. 21 | 22 | ## Shortcut Keys 23 | 24 | #### Window Operations 25 | 26 | - `command + 1`: Collapse expand folder list 27 | - `command + 2`: Expand the list of documents 28 | - `command + 3`: Switching between edit and preview states 29 | - `command + 4`: Switching between edit and presentation states 30 | 31 | #### File Operations 32 | 33 | - `command + n`:New document 34 | - `command + r`:Rename document 35 | - `command + d`:Copy document 36 | - `command + delete`:Delete document 37 | - `command + o`:Single open document separately 38 | - `command + shift + n`:New folder 39 | - `command + shift + l`:Auto typesetting 40 | - `command + option + r`:Displaying document in Finder 41 | - `command + option + i`:Display document attributes such as word count 42 | 43 | 🏂 There are also many other shortcuts waiting for you to find if you like to toss and turn~ 44 | 45 | ## Why do this 46 | 47 | - I have tried many note-taking applications before, such as WizNote, Ulysses, Quiver, MWeb, Bear, Typora, for various reasons, I did not find a conventional Markdown application, so I had the idea of doing MiaoYan. 48 | - My job is front-end development, but also can develop iOS App, love to toss new things, so develop MiaoYan as a fun leisure. 49 | 50 | ## Support 51 | 52 | - I have two cats, one is called TangYuan, and one is called Coke, If you think MiaoYan makes your life better, you can give my cats [feed canned food 🥩🍤](https://miaoyan.app/cats.html). 53 | - If you like MiaoYan, you can star it in Github. We are more welcome to recommend them to your like-minded friends. 54 | 55 | ## Thanks 56 | 57 | - KristopherGBaker/libcmark_gfm: Swift-compatible framework for cmark-gfm. 58 | - draveness/NightNight: Dark Mode. 59 | - raspu/Highlightr: Syntax highlighting capability. 60 | - glushchenko/fsnotes: MiaoYan has part of the framework code from this. 61 | - hpakovski/MASShortcut: Shortcut Key Plugin. 62 | - lxgw/LxgwWenKai: A beautiful open source Chinese font, MiaoYan has made it the default font. 63 | - sivan/heti:Enhanced typography for Chinese content presentation. 64 | 65 | # License 66 | 67 | - Follow the MIT License. 68 | - Please feel free to enjoy and participate in open source. 69 | -------------------------------------------------------------------------------- /Resources/Initial/MiaoYan PPT.md: -------------------------------------------------------------------------------- 1 | # Try command + option + p 2 | 3 | --- 4 | 5 | ## MiaoYan supports writing ppt quickly 🎉 6 | 7 | --- 8 | 9 | # How do you like it? 10 | - You can also select the document and right-click to start. 11 | - Miaoyan will recognize the document with '---' mark before it can be opened. 12 | - You can press "enter" to try, and you will see the preview outline. 13 | - Use [reveal](https://revealjs.com/markdown/), For more complex use, please refer to. 14 | 15 | --- 16 | 17 | 18 | # Let's change the color to see 19 | 20 | --- 21 | 22 | ### The control sequence is also very simple 23 | - Item 1: The last one appear 24 | - Item 2: The second appear 25 | - Item 3: First appear 26 | 27 | --- 28 | 29 | # It's easy to show the code 30 | 31 | ```js [1|2-4|5] 32 | import { withTable,useTable } from 'table-render'; 33 | const Page = () => { 34 | const { refresh } = useTable(); 35 | } 36 | export default withTable(Page) 37 | ``` 38 | 39 | --- 40 | 41 | # Have an awesome effect 42 |

Fade in

43 |

Fade out

44 |

Highlight red

45 |

Fade in, then out

46 |

Slide up while fading in

47 | 48 | --- 49 | 50 | 51 | 52 | 53 | --- 54 | 55 | 56 |

Hope can help you write wonderful words❤️

57 | -------------------------------------------------------------------------------- /Resources/Initial/介绍妙言.md: -------------------------------------------------------------------------------- 1 |

2 |

3 |

妙言

4 |
轻灵的 Markdown 笔记本伴你写出妙言~
5 |
中文 | English
6 |

7 | 8 | ## 特点 9 | 10 | - 🏂 **妙**:纯本地使用、安全、语法高亮、黑暗模式、源文件保存、国际化、演示模式、PPT 模式、单独编辑模式、文档自动排版、文档导出、内部跳转、图床、LaTeX、Mermaid、PlantUML 11 | - 🎊 **美**:极简的设计风格,文件夹 + 文件列表 + 编辑器方式 3 列模式 12 | - 🚄 **快**:使用 Swift5 原生开发,相比 Web 套壳方式性能体验好 13 | - 🥛 **简**:很轻巧,纯编辑器输入体验,众多快捷键助你快人一步 14 | 15 | ## 首次使用 16 | 17 | 1. 从 GitHub Releases 中下载最新的 MiaoYan.dmg 安装包,双击安装即可,如安装出现问题请参考 [文档](https://zhuanlan.zhihu.com/p/52389383)。 18 | 2. 可以在 iCloud 或根目录下创建一个 `MiaoYan` 的文件夹,打开妙言的设置,将默认存储地址修改成这个。 19 | 3. 点击妙言左上角新增文件夹的图标,创建好自己的文档分类文件夹,就可以开始使用了。 20 | 4. 同样假如你不习惯默认的字体,可以在设置中修改成其他的正常字体。 21 | 22 | ## 快捷键 23 | 24 | #### 窗口操作 25 | 26 | - `command + 1`:收起展开目录 27 | - `command + 2`:收起展开文档列表 28 | - `command + 3`:切换编辑和预览 29 | - `command + 4`:切换到演示模式 30 | 31 | #### 文件操作 32 | 33 | - `command + n`:新建文档 34 | - `command + r`:重命名文档 35 | - `command + d`:复制文档 36 | - `command + o`:单独打开文档 37 | - `command + delete`:删除文档 38 | - `command + shift + n`:新建文件夹 39 | - `command + shift + l`:自动排版 40 | - `command + option + r`:在 Finder 中显示 41 | - `command + option + i`:显示字数等文档属性 42 | 43 | 🏂 此外还有很多快捷键等着爱折腾的你去寻找~ 44 | 45 | ## 为什么要做妙言 46 | 47 | - 之前有尝试过众多的笔记应用,大学时期为知笔记、印象笔记,工作时候用过 Ulysses、Quiver、MWeb、Bear、Typora,种种原因,没有找到一个习惯的 Markdown 应用,才有了做妙言的想法。 48 | - 本职工作为前端开发,会一点 iOS 开发,喜欢折腾,借妙言来玩一下 Swift 以及 macOS 开发,当做一个很愉快的事情。 49 | 50 | ## 支持 51 | 52 | - 我有两只猫,一只叫汤圆,一只叫可乐,假如觉得妙言让你生活更美好,可以给汤圆可乐 [喂罐头 🥩🍤](https://miaoyan.app/cats.html)。 53 | - 如果你喜欢妙言,可以在 Github Star,更欢迎推荐给你志同道合的朋友使用。 54 | 55 | ## 感谢 56 | 57 | - KristopherGBaker/libcmark_gfm:适用于 cmark-gfm 的 Swift 兼容框架 58 | - draveness/NightNight:黑暗模式 59 | - raspu/Highlightr:语法高亮能力 60 | - glushchenko/fsnotes:妙言部分初始化代码来源于此 61 | - hpakovski/MASShortcut:快捷键插件 62 | - lxgw/LxgwWenKai:一款漂亮的开源中文字体,妙言将其作为默认字体 63 | - sivan/heti:专为中文内容展示设计的排版样式增强 64 | 65 | # 协议 66 | 67 | - 遵循 MIT 协议 68 | - 请自由地享受和参与开源 69 | -------------------------------------------------------------------------------- /Resources/Initial/妙言 PPT.md: -------------------------------------------------------------------------------- 1 | # command + option + p 试试 2 | 3 | --- 4 | 5 | # 妙言支持快速写 PPT 啦 🎉 6 | 7 | --- 8 | 9 | # 怎么样,喜欢不? 10 | - 也可以选择文档右键点击「妙言 PPT」启动 11 | - 妙言会识别带 `---` 标志的文档方可打开 12 | - 你可以按下「回车」试试,将看到预览大纲 13 | - 底层使用 [Reveal](https://revealjs.com/markdown/),更复杂使用可参考 14 | 15 | --- 16 | 17 | 18 | # 让我们改一个颜色看看 19 | 20 | --- 21 | 22 | # 控制顺序也很简单 23 | - Item 1:最后一个出现 24 | - Item 2:第二个出现 25 | - Item 3:第一个出现 26 | 27 | --- 28 | 29 | # 展示代码也好弄 30 | 31 | ```js [1|2-4|5] 32 | import { withTable,useTable } from 'table-render'; 33 | const Page = () => { 34 | const { refresh } = useTable(); 35 | } 36 | export default withTable(Page) 37 | ``` 38 | 39 | --- 40 | 41 | # 来一个牛逼的效果 42 |

Fade in

43 |

Fade out

44 |

Highlight red

45 |

Fade in, then out

46 |

Slide up while fading in

47 | 48 | --- 49 | 50 | 51 | 52 | 53 | --- 54 | 55 | 56 |

希望可以伴你写出妙言❤️

57 | -------------------------------------------------------------------------------- /Resources/Prettier/Configuration/ArrowFunctionParenthesesStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Strategy used when determining to include parentheses around a sole arrow function parameter. 4 | public enum ArrowFunctionParenthesesStrategy: String, Codable { 5 | /// Always include parens. Example: `(x) => x` 6 | case always 7 | /// Omit parens when possible. Example: `x => x` 8 | case avoid 9 | } 10 | -------------------------------------------------------------------------------- /Resources/Prettier/Configuration/ConfigurationKey.swift: -------------------------------------------------------------------------------- 1 | import JavaScriptCore 2 | 3 | enum ConfigurationKey: String { 4 | case parser = "parser" 5 | case rangeStart = "rangeStart" 6 | case rangeEnd = "rangeEnd" 7 | case cursorOffset = "cursorOffset" 8 | case plugins = "plugins" 9 | case printWidth = "printWidth" 10 | case tabWidth = "tabWidth" 11 | case useTabs = "useTabs" 12 | case semicolons = "semi" 13 | case singleQuote = "singleQuote" 14 | case quoteProperties = "quoteProps" 15 | case jsxSingleQuote = "jsxSingleQuote" 16 | case trailingCommas = "trailingComma" 17 | case bracketSpacing = "bracketSpacing" 18 | case bracketSameLine = "bracketSameLine" 19 | case arrowFunctionParentheses = "arrowParens" 20 | case proseWrap = "proseWrap" 21 | case htmlWhitespaceSensitivity = "htmlWhitespaceSensitivity" 22 | case vueIndentScriptAndStyle = "vueIndentScriptAndStyle" 23 | case endOfLine = "endOfLine" 24 | case embeddedLanguageFormatting = "embeddedLanguageFormatting" 25 | } 26 | 27 | extension JSValue { 28 | func setObject(_ object: Any!, forKeyedSubscript key: ConfigurationKey) { 29 | setObject(object, forKeyedSubscript: key.rawValue) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Resources/Prettier/Configuration/EmbeddedLanguageFormattingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Controls whether to format quoted code embedded in the file. 4 | public enum EmbeddedLanguageFormattingStrategy: String, Codable { 5 | /// Format embedded code if Prettier can automatically identify it. 6 | case auto 7 | /// Never automatically format embedded code. 8 | case off 9 | } 10 | -------------------------------------------------------------------------------- /Resources/Prettier/Configuration/EndOfLineStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Line endings to be used in formatted code. 4 | public enum EndOfLineStrategy: String, Codable { 5 | /// Line Feed only (\n). Common on Linux and macOS as well as inside git repositories. 6 | case lf 7 | /// Carriage Return + Line Feed characters (\r\n). Cmmon on Windows. 8 | case crlf 9 | /// Carriage Return character only (\r). Used very rarely. 10 | case cr 11 | /// Maintain existing line endings. Mixed values within one file are normalised by looking at what’s used after the first line. 12 | case auto 13 | } 14 | -------------------------------------------------------------------------------- /Resources/Prettier/Configuration/HTMLWhitespaceSensitivityStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The whitespace sensitivity for HTML, Vue, Angular, and Handlebars. 4 | public enum HTMLWhitespaceSensitivityStrategy: String, Codable { 5 | /// Respect the default value of CSS display property. For Handlebars treated same as `strict`. 6 | case css 7 | /// Whitespace (or the lack of it) around all tags is considered significant. 8 | case strict 9 | /// Whitespace (or the lack of it) around all tags is considered insignificant. 10 | case ignore 11 | } 12 | -------------------------------------------------------------------------------- /Resources/Prettier/Configuration/ProseWrapStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Strategy used when wrapping markdown text. 4 | public enum ProseWrapStrategy: String, Codable { 5 | /// Wrap prose if it exceeds the print width. 6 | case always 7 | /// Do not wrap prose. 8 | case never 9 | /// Wrap prose as-is. 10 | case preserve 11 | } 12 | -------------------------------------------------------------------------------- /Resources/Prettier/Configuration/QuotePropertyStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Strategy used when surrounding properties with quotes. 4 | public enum QuotePropertyStrategy: String, Codable { 5 | /// Only add quotes around object properties where required. 6 | case asNeeded = "as-needed" 7 | /// If at least one property in an object requires quotes, quote all properties. 8 | case consistent 9 | /// Respect the input use of quotes in object properties. 10 | case preserve 11 | } 12 | -------------------------------------------------------------------------------- /Resources/Prettier/Configuration/TrailingCommaStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Strategy determining whether to use trailing commas in formatted code. 4 | public enum TrailingCommaStrategy: String, Codable { 5 | /// Trailing commas where valid in ES5 (objects, arrays, etc.). No trailing commas in type parameters in TypeScript. 6 | case es5 7 | /// No trailing commas. 8 | case none 9 | /// railing commas wherever possible. 10 | case all 11 | } 12 | -------------------------------------------------------------------------------- /Resources/Prettier/FormatWithCursorResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Result of formatting code with a cursor position. 4 | public struct FormatWithCursorResult { 5 | /// The new cursor position. 6 | public let cursorOffset: Int 7 | /// The formatted string. 8 | public let formattedString: String 9 | 10 | init(cursorOffset: Int, formattedString: String) { 11 | self.cursorOffset = cursorOffset 12 | self.formattedString = formattedString 13 | } 14 | 15 | init?(object: [String: Any]) { 16 | guard let cursorOffset = object["cursorOffset"] as? Int else { 17 | return nil 18 | } 19 | guard let formattedString = object["formatted"] as? String else { 20 | return nil 21 | } 22 | self.cursorOffset = cursorOffset 23 | self.formattedString = formattedString 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Resources/Prettier/Parser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A parser that can be used with Prettier to format code. 4 | /// 5 | /// Parsers reside in a plugin and a plugin may contain one or more parsers. 6 | public protocol Parser { 7 | var name: String { get } 8 | } 9 | -------------------------------------------------------------------------------- /Resources/Prettier/ParsingErrorDetails.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Details supplied by PrettierFormatterError when parsing fails. 4 | public struct ParsingErrorDetails: CustomDebugStringConvertible { 5 | public struct Location { 6 | let line: Int 7 | let column: Int 8 | } 9 | 10 | public let line: Int 11 | public let column: Int 12 | public let codeFrame: String 13 | public var debugDescription: String { 14 | "Parsing error at line \(line), column \(column):\n\(codeFrame)" 15 | } 16 | 17 | init?(object: [String: Any]) { 18 | guard let codeFrame = object["codeFrame"] as? String else { 19 | return nil 20 | } 21 | guard let loc = object["loc"] as? [String: Any], let start = loc["start"] as? [String: Int] else { 22 | return nil 23 | } 24 | guard let line = start["column"] else { 25 | return nil 26 | } 27 | guard let column = start["column"] else { 28 | return nil 29 | } 30 | self.line = line 31 | self.column = column 32 | self.codeFrame = codeFrame 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Resources/Prettier/Plugin.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A plugin that can be passed to PrettierFormatter to format code. 4 | /// 5 | /// One plugin may contain one or more parsers. 6 | public protocol Plugin { 7 | var fileURL: URL { get } 8 | } 9 | -------------------------------------------------------------------------------- /Resources/Prettier/PrettierMarkdown/MarkdownParser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct MarkdownParser: Parser { 4 | public let name = "markdown" 5 | 6 | public init() {} 7 | } 8 | -------------------------------------------------------------------------------- /Resources/Prettier/PrettierMarkdown/MarkdownPlugin.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct MarkdownPlugin: Plugin { 4 | public var fileURL: URL 5 | public init() { 6 | let path = Bundle.main.path(forResource: "Prettier", ofType: ".bundle") 7 | let url = NSURL.fileURL(withPath: path!) 8 | let bundle = Bundle(url: url) 9 | fileURL = bundle!.url(forResource: "parser-markdown", withExtension: "js")! 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Resources/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "miaoyan", 3 | "version": "1.6.0", 4 | "description": "miaoyan static resource", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/tw93/MiaoYan.git" 12 | }, 13 | "keywords": [ 14 | "miaoyan" 15 | ], 16 | "author": "Tw93", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/tw93/MiaoYan/issues" 20 | }, 21 | "homepage": "https://github.com/tw93/MiaoYan#readme" 22 | } 23 | -------------------------------------------------------------------------------- /en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "Add" = "添加"; 3 | 4 | /* No comment provided by engineer. */ 5 | "Are you sure you want to irretrievably delete %d note(s)?" = "确定要永久删除%d个笔记?"; 6 | 7 | /* No comment provided by engineer. */ 8 | "Are you sure you want to remove project \"%@\" and all files inside?" = "您确定要删除项目\"%@\"和它下面的所有文件吗?"; 9 | 10 | /* Delete menu */ 11 | "Cancel" = "取消"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Copy HTML" = "拷贝HTML"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Copy Link" = "拷贝链接"; 18 | 19 | /* No comment provided by engineer. */ 20 | "Copy Plain Text" = "拷贝纯文本"; 21 | 22 | /* No comment provided by engineer. */ 23 | "Delete" = "删除"; 24 | 25 | /* No comment provided by engineer. */ 26 | "Delete folder" = "删除文件夹"; 27 | 28 | 29 | /* No comment provided by engineer. */ 30 | "MiaoYan [preview]" = "妙言 [预览]"; 31 | 32 | /* No comment provided by engineer. */ 33 | "Link" = "链接"; 34 | 35 | /* Menu */ 36 | "Move" = "移动"; 37 | 38 | /* No comment provided by engineer. */ 39 | "New" = "新建"; 40 | 41 | /* No comment provided by engineer. */ 42 | "New folder" = "新建文件夹"; 43 | 44 | /* No comment provided by engineer. */ 45 | "New project" = "新建文件夹"; 46 | 47 | /* Sidebar label */ 48 | "Notes" = "妙言"; 49 | 50 | /* No comment provided by engineer. */ 51 | "OK" = "好的"; 52 | 53 | /* Edit area */ 54 | "Please enter image title:" = "请输入图片标题:"; 55 | 56 | /* No comment provided by engineer. */ 57 | "Please enter project name:" = "请输入名称:"; 58 | 59 | /* No comment provided by engineer. */ 60 | "Please try again" = "请再试一次"; 61 | 62 | /* No comment provided by engineer. */ 63 | "Preferences" = "偏好设置"; 64 | 65 | /* No comment provided by engineer. */ 66 | "Quit MiaoYan" = "退出妙言"; 67 | 68 | /* Delete menu */ 69 | "Remove" = "移除"; 70 | 71 | /* No comment provided by engineer. */ 72 | "Remove note(s)" = "确定删除"; 73 | 74 | /* No comment provided by engineer. */ 75 | "Rename folder" = "重命名文件夹"; 76 | 77 | /* No comment provided by engineer. */ 78 | "Show in Finder" = "在Finder中显示"; 79 | 80 | /* View menu */ 81 | "Sort by" = "排序方式"; 82 | 83 | /* No comment provided by engineer. */ 84 | "Strikethrough" = "删除线"; 85 | 86 | /* Delete menu */ 87 | "This action cannot be undone." = "此操作无法恢复。"; 88 | 89 | /* No comment provided by engineer. */ 90 | "Toggle preview" = "切换预览"; 91 | 92 | /* Sidebar label */ 93 | "Trash" = "删除"; 94 | 95 | /* No comment provided by engineer. */ 96 | "Underline" = "下划线"; 97 | 98 | /* Untitled Note */ 99 | "Untitled Note" = "无标题"; 100 | 101 | /* No comment provided by engineer. */ 102 | "URL has been copied to clipboard" = "URL已复制到剪贴板"; 103 | 104 | /* Menu */ 105 | "View" = "视图"; 106 | 107 | "Search" = "搜索"; 108 | 109 | "MiaoYan" = "妙言"; 110 | 111 | "🎉 Automatic typesetting succeeded~" = "🎉 自动化排版完成~"; 112 | 113 | "🙊 Press ESC key to exit~" = "🙊 按 ESC 键退出演示~"; 114 | 115 | "😶‍🌫 Format is only possible after exiting preview mode~" = "😶‍🌫 文档排版需要退出预览模式才可使用~"; 116 | 117 | "😶‍🌫 The current Mac system does not support export, please upgrade to above 11.0~" = "😶‍🌫 当前 Mac 系统不支持导出,请升级到 11.0 以上~"; 118 | 119 | "🎉 Exported to desktop~" = "🎉 已导出到桌面~"; 120 | 121 | "🙊 Starting export~" = "🙊 开始导出中~"; 122 | 123 | "🍭 Image upload in progress~" = "🍭 图片上传中~"; 124 | 125 | "😶‍🌫 Image upload failed, Use local~" = "😶‍🌫 图片上传失败,优先使用本地,可手动上传~"; 126 | 127 | "%d MiaoYan" = "%d 篇妙言"; 128 | 129 | "🙊 Please make sure your Mac is installed %@ ~" = "🙊 请确保你的 Mac 已安装并配置好了 %@ ~"; 130 | 131 | "🙊 In single open mode, Exit with Command+Shift+W ~" = "🙊 单独编辑器模式中,可 Command+Shift+W 退出~"; 132 | 133 | "😶‍🌫 Please make sure your title exists~" = "😶‍🌫 请确定你填写的标题是否存在~"; 134 | 135 | "🍭 Found that there are multiple titles of this~" = "🍭 发现这里有多个标题,使用搜索能力~"; 136 | 137 | "🎉 URL is successfully copied, Use it anywhere~" = "🎉 外链成功复制,你可以在任何地方使用~"; 138 | 139 | "😶‍🌫 No delimiter --- identification, Cannot use MiaoYan PPT~" = "😶‍🌫 没有分隔符 --- 标识,无法使用妙言 PPT~"; 140 | 141 | "This %@ under this folder already exists!" = "此文件夹下 %@ 已经存在!"; 142 | "Please change the title" = "请换一个标题"; 143 | 144 | "Edit Link…" = "编辑链接"; 145 | "Remove Link" = "移除链接"; 146 | 147 | "Restart to MiaoYan to take effect" = "重新启动妙言后生效"; 148 | "Confirm" = "确定"; 149 | -------------------------------------------------------------------------------- /ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "Add" = "追加"; 3 | 4 | /* No comment provided by engineer. */ 5 | "Are you sure you want to irretrievably delete %d note(s)?" = "%dのメモを削除しても宜しいでしょうか?"; 6 | 7 | /* No comment provided by engineer. */ 8 | "Are you sure you want to remove project \"%@\" and all files inside?" = "フォルダ\"%@\"と中の全てファイルを削除してもよろしいですか?"; 9 | 10 | /* Delete menu */ 11 | "Cancel" = "キャンセル"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Copy HTML" = "HTMLをコピー"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Copy Link" = "リンクをコピー"; 18 | 19 | /* No comment provided by engineer. */ 20 | "Copy Plain Text" = "プレーンテキストをコピー"; 21 | 22 | /* No comment provided by engineer. */ 23 | "Delete" = "削除"; 24 | 25 | /* No comment provided by engineer. */ 26 | "Delete Folder" = "フォルダを削除"; 27 | 28 | 29 | /* No comment provided by engineer. */ 30 | "MiaoYan [preview]" = "妙言を「プレビュー」"; 31 | 32 | /* No comment provided by engineer. */ 33 | "Link" = "リンク"; 34 | 35 | /* Menu */ 36 | "Move" = "移動"; 37 | 38 | /* Menu */ 39 | "Export" = "輸出"; 40 | 41 | /* No comment provided by engineer. */ 42 | "New" = "新規作成"; 43 | 44 | /* No comment provided by engineer. */ 45 | "New Folder" = "新規フォルダ"; 46 | 47 | /* No comment provided by engineer. */ 48 | "New project" = "新規フォルダ"; 49 | 50 | /* Sidebar label */ 51 | "Notes" = "妙言"; 52 | 53 | /* No comment provided by engineer. */ 54 | "OK" = "はい"; 55 | 56 | /* Edit area */ 57 | "Please enter image title:" = "画像のタイトルを入力してください:"; 58 | 59 | /* No comment provided by engineer. */ 60 | "Please enter project name:" = "プロジェクト名を入力してください:"; 61 | 62 | /* No comment provided by engineer. */ 63 | "Please try again" = "もう一度お試しください"; 64 | 65 | /* No comment provided by engineer. */ 66 | "Preferences" = "環境設定"; 67 | 68 | /* No comment provided by engineer. */ 69 | "Quit MiaoYan" = "妙言を終了する"; 70 | 71 | /* Delete menu */ 72 | "Remove" = "削除"; 73 | 74 | /* No comment provided by engineer. */ 75 | "Remove note(s)" = "メモを削除しても宜しいでしょうか"; 76 | 77 | /* No comment provided by engineer. */ 78 | "Rename Folder" = "フォルダの名前を変更"; 79 | 80 | /* No comment provided by engineer. */ 81 | "Show in Finder" = "Finderに表示"; 82 | 83 | /* View menu */ 84 | "Sort by" = "並び替え"; 85 | 86 | /* No comment provided by engineer. */ 87 | "Strikethrough" = "取り消し線"; 88 | 89 | /* Delete menu */ 90 | "This action cannot be undone." = "この操作は元に戻せません。宜しいでしょうか?"; 91 | 92 | /* No comment provided by engineer. */ 93 | "Toggle Preview" = "プレビューを切り替え"; 94 | 95 | /* Sidebar label */ 96 | "Trash" = "削除"; 97 | 98 | /* No comment provided by engineer. */ 99 | "Underline" = "アンダーライン"; 100 | 101 | /* Untitled Note */ 102 | "Untitled Note" = "名称未設定"; 103 | 104 | /* No comment provided by engineer. */ 105 | "URL has been copied to clipboard" = "URLがクリップボードにコピーされました。"; 106 | 107 | /* Menu */ 108 | "View" = "视图"; 109 | 110 | "Search" = "検索"; 111 | 112 | "MiaoYan" = "妙言"; 113 | 114 | "🎉 Automatic typesetting succeeded~" = "🎉 自動組版完了しました。"; 115 | 116 | "🙊 Press ESC key to exit~" = "🙊 ESC キーを押して終了します。"; 117 | 118 | "😶‍🌫 Format is only possible after exiting preview mode~" = "😶‍🌫 フォーマットの為に、プレビューモードを終了してください。"; 119 | 120 | "😶‍🌫 The current Mac system does not support export, please upgrade to above 11.0~" = "😶‍🌫 現在利用の環境は出力をサポートしていません。11.0にアップグレードしてください。"; 121 | 122 | "🎉 Saved to Downloads folder~" = "🎉 ダウンロードフォルダに保存されました。"; 123 | 124 | "🙊 Starting export~" = "🙊 出力開始〜"; 125 | 126 | "🍭 Image upload in progress~" = "🍭 画像アップロード中~"; 127 | 128 | "😶‍🌫 Image upload failed, Use local~" = "😶‍🌫 画像のアップロードに失敗しました。ローカルを使用してください"; 129 | 130 | "%d MiaoYan" = "%d つ妙言"; 131 | 132 | "🙊 Please make sure your Mac is installed %@ ~" = "🙊 Macに %@ をインストールされていることを確認してください。"; 133 | 134 | "🙊 In single open mode, Exit with Command+Shift+W ~" = "🙊 シングルモードでは、Command+Shift+W で終了します"; 135 | 136 | "😶‍🌫 Please make sure your title exists~" = "😶‍🌫 タイトルが既にあることを確認してください"; 137 | 138 | "🍭 Found that there are multiple titles of this~" = "🍭 複数のタイトルが見つかりました、検索を利用ください。"; 139 | 140 | "🎉 URL is successfully copied, Use it anywhere~" = "🎉 URLがコピーされました。ご利用ください。"; 141 | 142 | "😶‍🌫 No delimiter --- identification, Cannot use MiaoYan PPT~" = "😶‍🌫 区切り「---」がなければ、MiaoYan PPT使えません。"; 143 | 144 | "This %@ under this folder already exists!" = "このフォルダの下に %@ は既に存在します"; 145 | "Please change the title" = "タイトルを変更してください。"; 146 | 147 | "Edit Link…" = "リンクを編集"; 148 | "Remove Link" = "リンクを削除"; 149 | 150 | "Restart to MiaoYan to take effect" = "妙言を再起動して有効にします。"; 151 | "Confirm" = "確認"; 152 | -------------------------------------------------------------------------------- /zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "Add" = "添加"; 3 | 4 | /* No comment provided by engineer. */ 5 | "Are you sure you want to irretrievably delete %d note(s)?" = "确定要永久删除%d个笔记?"; 6 | 7 | /* No comment provided by engineer. */ 8 | "Are you sure you want to remove project \"%@\" and all files inside?" = "您确定要删除项目\"%@\"和它下面的所有文件吗?"; 9 | 10 | /* Delete menu */ 11 | "Cancel" = "取消"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Copy HTML" = "拷贝HTML"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Copy Link" = "拷贝链接"; 18 | 19 | /* No comment provided by engineer. */ 20 | "Copy Plain Text" = "拷贝纯文本"; 21 | 22 | /* No comment provided by engineer. */ 23 | "Delete" = "删除"; 24 | 25 | /* No comment provided by engineer. */ 26 | "Delete Folder" = "删除文件夹"; 27 | 28 | /* No comment provided by engineer. */ 29 | "MiaoYan [preview]" = "妙言 [预览]"; 30 | 31 | /* No comment provided by engineer. */ 32 | "Link" = "链接"; 33 | 34 | /* Menu */ 35 | "Move" = "移动"; 36 | 37 | /* Menu */ 38 | "Export" = "导出"; 39 | 40 | /* No comment provided by engineer. */ 41 | "New" = "新建"; 42 | 43 | /* No comment provided by engineer. */ 44 | "New Folder" = "新建文件夹"; 45 | 46 | /* No comment provided by engineer. */ 47 | "New project" = "新建文件夹"; 48 | 49 | /* Sidebar label */ 50 | "Notes" = "妙言"; 51 | 52 | /* No comment provided by engineer. */ 53 | "OK" = "好的"; 54 | 55 | /* Edit area */ 56 | "Please enter image title:" = "请输入图片标题:"; 57 | 58 | /* No comment provided by engineer. */ 59 | "Please enter project name:" = "请输入名称:"; 60 | 61 | /* No comment provided by engineer. */ 62 | "Please try again" = "请再试一次"; 63 | 64 | /* No comment provided by engineer. */ 65 | "Preferences" = "偏好设置"; 66 | 67 | /* No comment provided by engineer. */ 68 | "Quit MiaoYan" = "退出妙言"; 69 | 70 | /* Delete menu */ 71 | "Remove" = "移除"; 72 | 73 | /* No comment provided by engineer. */ 74 | "Remove note(s)" = "确定删除"; 75 | 76 | /* No comment provided by engineer. */ 77 | "Rename Folder" = "重命名文件夹"; 78 | 79 | /* No comment provided by engineer. */ 80 | "Show in Finder" = "在Finder中显示"; 81 | 82 | /* View menu */ 83 | "Sort by" = "排序方式"; 84 | 85 | /* No comment provided by engineer. */ 86 | "Strikethrough" = "删除线"; 87 | 88 | /* Delete menu */ 89 | "This action cannot be undone." = "此操作无法恢复。"; 90 | 91 | /* No comment provided by engineer. */ 92 | "Toggle Preview" = "切换预览"; 93 | 94 | /* Sidebar label */ 95 | "Trash" = "删除"; 96 | 97 | /* No comment provided by engineer. */ 98 | "Underline" = "下划线"; 99 | 100 | /* Untitled Note */ 101 | "Untitled Note" = "无标题"; 102 | 103 | /* No comment provided by engineer. */ 104 | "URL has been copied to clipboard" = "URL已复制到剪贴板"; 105 | 106 | /* Menu */ 107 | "View" = "视图"; 108 | 109 | "Search" = "搜索"; 110 | 111 | "MiaoYan" = "妙言"; 112 | 113 | "🎉 Automatic typesetting succeeded~" = "🎉 自动化排版完成~"; 114 | 115 | "🙊 Press ESC key to exit~" = "🙊 按 ESC 键退出演示~"; 116 | 117 | "😶‍🌫 Format is only possible after exiting preview mode~" = "😶‍🌫 文档排版需要退出预览模式才可使用~"; 118 | 119 | "😶‍🌫 The current Mac system does not support export, please upgrade to above 11.0~" = "😶‍🌫 当前 Mac 系统不支持导出,请升级到 11.0 以上~"; 120 | 121 | "🎉 Saved to Downloads folder~" = "🎉 已导出到下载目录~"; 122 | 123 | "🙊 Starting export~" = "🙊 开始导出中~"; 124 | 125 | "🍭 Image upload in progress~" = "🍭 图片上传中~"; 126 | 127 | "😶‍🌫 Image upload failed, Use local~" = "😶‍🌫 图片上传失败,优先使用本地,可手动上传~"; 128 | 129 | "%d MiaoYan" = "%d 篇妙言"; 130 | 131 | "🙊 Please make sure your Mac is installed %@ ~" = "🙊 请确保你的 Mac 已安装并配置好了 %@ ~"; 132 | 133 | "🙊 In single open mode, Exit with Command+Shift+W ~" = "🙊 单独编辑器模式中,可 Command+Shift+W 退出~"; 134 | 135 | "😶‍🌫 Please make sure your title exists~" = "😶‍🌫 请确定你填写的标题是否存在~"; 136 | 137 | "🍭 Found that there are multiple titles of this~" = "🍭 发现这里有多个标题,使用搜索能力~"; 138 | 139 | "🎉 URL is successfully copied, Use it anywhere~" = "🎉 外链成功复制,你可以在任何地方使用~"; 140 | 141 | "😶‍🌫 No delimiter --- identification, Cannot use MiaoYan PPT~" = "😶‍🌫 没有分隔符 --- 标识,无法使用妙言 PPT~"; 142 | 143 | "This %@ under this folder already exists!" = "此文件夹下 %@ 已经存在!"; 144 | "Please change the title" = "请换一个标题"; 145 | 146 | "Edit Link…" = "编辑链接"; 147 | "Remove Link" = "移除链接"; 148 | 149 | "Restart to MiaoYan to take effect" = "重新启动妙言后生效"; 150 | "Confirm" = "确定"; 151 | --------------------------------------------------------------------------------