├── .python-version ├── Default.sublime-commands ├── LICENSE ├── OutlineNotesPublisher.py └── README.md /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 2 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Outline to HTML: Create from selection", 4 | "command": "outline_to_html" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /OutlineNotesPublisher.py: -------------------------------------------------------------------------------- 1 | import sublime, sublime_plugin 2 | import re 3 | 4 | """ 5 | Configure using Preferences ➡ Settings 6 | { 7 | // ... 8 | "outline_to_html": { 9 | // "css": "", // Override CSS. 10 | // "title": "", // Override Title (or use meta comment //title ...) 11 | // "header": "", // Add to
12 | // "body": "", // Add to 13 | // "footer": "", // Add before 14 | } 15 | } 16 | """ 17 | 18 | class OutlineToHtml(sublime_plugin.TextCommand): 19 | # Default settings. 20 | HTML_TITLE = "Hello World" # Replace using //title New Title 21 | HTML_CSS = """ 22 | body { font-size: 1em; font-family: "sans"; background: #222; color: #aaa; padding: 2rem 2.4rem; } 23 | ul { list-style-type: circle; } 24 | a { color: white; } 25 | """ 26 | HTML_HEADER_EXTRA = '' 27 | HTML_BODY_EXTRA = '' 28 | HTML_FOOTER_EXTRA = '' 29 | INDENT_TABS = '\t' # One tab is typical. 30 | INDENT_SPACES = ' ' # If you use something other than 4 spaces. 31 | WS = INDENT_TABS # Whitespace. 32 | HTML_WS = f"\n{WS*2}" 33 | 34 | def run(self, edit): 35 | s = OutlineToHtml 36 | # Can be set by User Preferences. 37 | _s = sublime.load_settings("Preferences.sublime-settings") 38 | s.HTML_TITLE = _s.get("outline_to_html", {}).get("title", s.HTML_TITLE) 39 | s.HTML_CSS = _s.get("outline_to_html", {}).get("css", s.HTML_CSS) 40 | s.HTML_HEADER_EXTRA = _s.get("outline_to_html", {}).get("header", s.HTML_HEADER_EXTRA) 41 | s.HTML_BODY_EXTRA = _s.get("outline_to_html", {}).get("body", s.HTML_BODY_EXTRA) 42 | s.HTML_FOOTER_EXTRA = _s.get("outline_to_html", {}).get("footer", s.HTML_FOOTER_EXTRA) 43 | 44 | parser = OutlineToHtmlCreator() 45 | 46 | # Get current selection 47 | sels = self.view.sel() 48 | sels_parsed = 0 49 | if(len(sels) > 0): 50 | for sel in sels: 51 | # Make sure selection isn't just a cursor 52 | if(abs(sel.b - sel.a) > 0): 53 | self.fromRegion(parser, sel, edit) 54 | sels_parsed += 1 55 | 56 | # All selections just cursor marks? 57 | if(sels_parsed == 0): 58 | region = sublime.Region(0, self.view.size() - 1) 59 | self.fromRegion(parser, region, edit) 60 | 61 | def fromRegion(self, parser, region, edit): 62 | lines = self.view.line(region) 63 | text = self.view.substr(lines) 64 | indented = parser.fromSelection(text) 65 | newview = self.view.window().new_file() 66 | newview.insert(edit, 0, indented) 67 | 68 | class OutlineToHtmlCreator: 69 | def fromSelection(self, text): 70 | return self.create(text.split("\n")) 71 | 72 | def tagify(self, text, token, token_end, formatter, remove_token=False): 73 | pos = 0 74 | found = True 75 | while found: 76 | found = False 77 | ''' Attempt to convert tokens into tags. ''' 78 | if (pos := text.find(token, pos)) >= 0: 79 | found = True 80 | url = '' 81 | url_name = '' 82 | url_end = 0 83 | token_remove = len(token) 84 | # Bail if this token is inside a quote (ex: HTML tag attribute) 85 | if text[pos-1] == '"' or text[pos-1] == "'": 86 | pos = pos+1 87 | continue 88 | # Bail if this ")[" token does not have supporting "[" or ")" 89 | if token == "](": 90 | if text.find('[', 0, pos) == -1 or text.find(')', pos) == -1: 91 | return text 92 | # Extract name. 93 | if token == "](": 94 | url_name = text[text.find('[', 0, pos)+1:pos] 95 | # Extract URL 96 | partial = text[pos:] 97 | for i,c in enumerate(partial): 98 | if c.isspace() or c == token_end: # Will end at whitespace or token_end 99 | url_end = pos+i 100 | break 101 | if not url_end: # We reached EOL 102 | url_end = len(text) 103 | url = text[pos+token_remove:url_end] 104 | if token == "](": # Special case. 105 | text = text[:text.find('[', 0, pos)] + formatter.format(url, url_name) + text[url_end+len(token_end):] 106 | else: 107 | text = text[:pos] + formatter.format(url, url_name) + text[url_end:] 108 | return text 109 | 110 | def create(self, text_iterable): 111 | s = OutlineToHtml 112 | indent_level_previous = 0 113 | inside_code_block = False 114 | output = "" 115 | 116 | # Compile whitespace regex for reuse. 117 | regex_indent = re.compile(f"^({s.INDENT_TABS}|{s.INDENT_SPACES})") 118 | 119 | # Parse text 120 | for line in text_iterable: 121 | 122 | # ```lang Code block. 123 | if(inside_code_block or line.find("```") == 0): 124 | if not inside_code_block and line.find("```") == 0: 125 | if line == "```\n": 126 | language = 'html' 127 | else: 128 | line = line.replace("```", "") 129 | language = line.split("\n")[0] 130 | output += f""
131 | inside_code_block = True
132 | continue
133 | # End of code block.
134 | elif inside_code_block and line.find("```") == 0:
135 | line = line.replace("```", "")
136 | output += f"{line}
"
137 | inside_code_block = False
138 | continue
139 | else:
140 | # Inside code block.
141 | output += f"{line}\n"
142 | continue
143 |
144 | # // Metadata comments.
145 | if(line.find("//title ") == 0):
146 | line = line.replace("//title ", "")
147 | s.HTML_TITLE = line
148 | continue
149 |
150 | # // Comments.
151 | if(line.find("//") == 0):
152 | continue # Skip line.
153 |
154 | # Levels of indentation
155 | indent_level = 0
156 |
157 | while(regex_indent.match(line)):
158 | line = regex_indent.sub("", line)
159 | indent_level += 1
160 |
161 | indentDiff = indent_level - indent_level_previous
162 |
163 | # Does a new level of indentation need to be created?
164 | if(indentDiff >= 1):
165 | output += f"{s.HTML_WS}{s.WS*(indent_level-1)}