├── .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: ")")
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 |
2 |
3 |
4 |
5 |
妙言
6 |
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 |
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 |
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 |
--------------------------------------------------------------------------------