1
0
Fork 0
mirror of https://github.com/marty-oehme/scripts.git synced 2024-12-22 07:58:08 +00:00

New version

This commit is contained in:
Maboroshy 2017-09-03 20:29:59 +04:00 committed by GitHub
parent 03d7a8ca37
commit 3eb80115a4
6 changed files with 292 additions and 70 deletions

View file

@ -2,19 +2,20 @@
import os
import re
import sys
import time
import argparse
import platform
import collections
import multiprocessing.dummy
import md_link
import md_convert
import safe_path
def text_to_md(file_attrs):
"""
This will process specified text file getting its tags and replacing urls with favicons and titles where possible
This will process specified text file getting its topics and replacing urls with favicons and titles where possible
:param file_attrs: File_attrs named tuple
:return: list of Note_attrs named tuple
"""
@ -24,8 +25,8 @@ def text_to_md(file_attrs):
with open(file_attrs.file_path, 'r') as text_file:
text = text_file.read()
tags = re.findall(file_attrs.tag_marker + '(\w*)', text)
text = re.sub(file_attrs.tag_marker + '\w*[ ]?', '', text).strip()
topics = re.findall(file_attrs.topic_marker + '(\w*)', text)
text = re.sub(file_attrs.topic_marker + '\w*[ ]?', '', text).strip()
if re.match('^http[s]?://[^\s]*$', text):
is_bookmark = True
@ -38,18 +39,24 @@ def text_to_md(file_attrs):
if is_bookmark:
bookmark_title = url.title
if file_attrs.inbox_file:
output_files = [file_attrs.inbox_file]
if file_attrs.output_file and file_attrs.output_file != '*no mtime*':
output_files = [file_attrs.output_file]
headline_title = ''
elif tags:
output_files = [tag + '.md' for tag in tags]
elif topics:
output_files = [topic + '.md' for topic in topics]
headline_title = ''
elif is_bookmark:
output_files = [time.strftime('%m-%d %H:%M', mtime) + ' ' + bookmark_title + '.md']
headline_title = '# {}\n'.format(bookmark_title)
if file_attrs.output_file == '*no mtime*':
output_files = [bookmark_title + '.md']
else:
output_files = [time.strftime('%m-%d %H:%M', mtime) + ' ' + bookmark_title + '.md']
else:
output_files = [time.strftime('%m-%d %H:%M', mtime) + ' ' + filename + '.md']
headline_title = '# {}\n'.format(filename)
if file_attrs.output_file == '*no mtime*':
output_files = [filename + '.md']
else:
output_files = [time.strftime('%m-%d %H:%M', mtime) + ' ' + filename + '.md']
output = []
for output_file in output_files:
@ -60,6 +67,24 @@ def text_to_md(file_attrs):
title=headline_title))
return output
def html_to_md(file_attrs, pandoc_bin='pandoc', pandoc_ver=''):
"""
This will move specified convert specified html file to markdown and move all in-line images to sub-folder at media directory
:param file_attrs: File_attrs named tuple
:return: Note_attrs named tuple
"""
html_file_name_noext = os.path.splitext(os.path.basename(file_attrs.file_path))[0]
mtime = time.localtime(os.path.getmtime(file_attrs.file_path))
md_text = md_convert.saved_html(file_attrs.file_path, file_attrs.folder_dir_path,
pandoc_bin=pandoc_bin, pandoc_ver=pandoc_ver)
if not md_text:
return
return Note_attrs(input_file_path=file_attrs.file_path,
output_file_path=file_attrs.output_dir_path + os.sep + safe_path.filename(html_file_name_noext + '.md'),
text=md_text,
mtime='**{}** \n'.format(time.strftime('%x %a %X', mtime)),
title='')
def file_to_md(file_attrs, media_dir_name):
"""
@ -79,8 +104,10 @@ def file_to_md(file_attrs, media_dir_name):
file = md_link.File(new_path, file_attrs.folder_dir_path, os.path.splitext(os.path.basename(file_attrs.file_path))[0])
if file_attrs.inbox_file:
output_file = file_attrs.inbox_file
if file_attrs.output_file == '*no mtime*':
output_file = file.title + '.md'
elif file_attrs.output_file:
output_file = file_attrs.output_file
else:
output_file = time.strftime('%m-%d %H:%M', mtime) + ' ' + file.title + '.md'
@ -93,15 +120,16 @@ def file_to_md(file_attrs, media_dir_name):
if __name__ == '__main__':
File_attrs = collections.namedtuple('File_attrs', 'file_path folder_dir_path output_dir_path tag_marker inbox_file')
File_attrs = collections.namedtuple('File_attrs', 'file_path folder_dir_path output_dir_path topic_marker output_file')
"""
A named tuple which functions use to pass input data - data of files to be processed
:param file_path: full absolute path to the file to process
:param folder_dir_path: full absolute path to directory where 'media' and 'attachment' directories are
:param output_dir_path: full absolute path to directory where resulting text file will be stored
:param tag_marker: symbol(s) which start the tag word (for text files)
:param inbox_file: full absolute path to the text file which will be appended with a new entry,
if none the entry will go to new standalone text file
:param topic_marker: symbol(s) which start the 'topic' word (for text files)
:param output_file: empty for new standalone text file with mtime in the name,
'*no mtime*' for or new standalone text file without mtime in the name
or full absolute path to the text file which will be appended with a new entry
"""
Note_attrs = collections.namedtuple('Note_attrs', 'input_file_path output_file_path text mtime title')
@ -118,17 +146,35 @@ if __name__ == '__main__':
:param file_attrs: File_attrs named tuple
:return: Note_attrs named tuple
"""
if file_attrs.file_path.endswith('.txt') or not os.path.splitext(file_attrs.file_path)[1]:
return text_to_md(file_attrs)
elif args.pandoc_bin and args.pandoc_ver and file_attrs.file_path.endswith(('.htm', '.html')):
return html_to_md(file_attrs, args.pandoc_bin, args.pandoc_ver)
elif file_attrs.file_path.endswith(('.jpg', '.png', '.gif')):
return file_to_md(file_attrs, 'media')
else:
return file_to_md(file_attrs, 'attachments')
inbox_dir = sys.argv[1]
folder_dir = sys.argv[2]
tag_marker = sys.argv[3]
arg_parser = argparse.ArgumentParser(description='A script to turn everything in the inbox directory to markdown notes.')
arg_parser.add_argument('-i', '--inbox', action='store', dest='inbox_dir', required=True,
help="Full absolute path to the inbox directory to organize")
arg_parser.add_argument('-f', '--folder', action='store', dest='folder_dir', required=True,
help="Full absolute path to directory where 'media' and 'attachment' directories are")
arg_parser.add_argument('-m', '--marker', action='store', dest='topic_marker', required=False, default='@',
help="Symbol(s) which start the 'topic' word (for text files)")
arg_parser.add_argument('-s', '--scan-folder', action='store_true', dest='scan_folder', required=False,
help="Process whole folder rather than only inbox")
arg_parser.add_argument('-p', '--pandoc-bin', action='store', dest='pandoc_bin', required=False,
help="Command/path to run pandoc")
arg_parser.add_argument('-pv', '--pandoc-ver', action='store', dest='pandoc_ver', required=False,
help="Installed pandoc version")
args = arg_parser.parse_args()
inbox_dir = args.inbox_dir
folder_dir = args.folder_dir
topic_marker = args.topic_marker
os.makedirs(inbox_dir, exist_ok=True)
os.makedirs(folder_dir + os.sep + 'media', exist_ok=True)
@ -136,27 +182,35 @@ if __name__ == '__main__':
# Prepare a list of File_attrs tuples for process_by_ext function, based on file location, older files first
file_list = []
if args.scan_folder:
for file_path in sorted([folder_dir + os.sep + path for path in os.listdir(folder_dir)], key=os.path.getmtime):
if os.path.isfile(file_path) and not file_path.endswith(('.md', 'notes.sqlite')) \
and not os.path.basename(file_path).startswith(('.', inbox_dir, folder_dir + os.sep + 'media', folder_dir + os.sep + 'attachments')):
file_list.append([File_attrs(file_path=file_path, folder_dir_path=folder_dir, output_dir_path=os.path.dirname(file_path),
topic_marker=topic_marker, output_file='*no mtime*')])
for file_path in sorted([inbox_dir + os.sep + path for path in os.listdir(inbox_dir)], key=os.path.getmtime):
if os.path.isdir(file_path) and not os.path.basename(file_path).startswith('.'):
if os.path.isdir(file_path) and not os.path.basename(file_path).startswith('.') and not file_path.endswith('_files'):
for sub_file in sorted([file_path + os.sep + path for path in os.listdir(file_path)], key=os.path.getmtime):
if not sub_file.endswith('.md') and not os.path.basename(sub_file).startswith('.'):
file_list.append([File_attrs(file_path=sub_file, folder_dir_path=folder_dir, output_dir_path=inbox_dir,
tag_marker=tag_marker, inbox_file=os.path.basename(file_path) + '.md')])
topic_marker=topic_marker, output_file=os.path.basename(file_path) + '.md')])
else:
if not file_path.endswith('.md') and not os.path.basename(file_path).startswith('.'):
if os.path.isfile(file_path) and not file_path.endswith('.md') and not os.path.basename(file_path).startswith('.'):
file_list.append([File_attrs(file_path=file_path, folder_dir_path=folder_dir, output_dir_path=inbox_dir,
tag_marker=tag_marker, inbox_file='')])
topic_marker=topic_marker, output_file='')])
# Run process_by_ext for each File_attrs tuple putting resulted Note_attrs tuples to write_list
write_list = multiprocessing.dummy.Pool().starmap(process_by_ext, file_list)
# Run process_by_ext for each File_attrs tuple putting resulting Note_attrs tuples to write_list
write_list = multiprocessing.dummy.Pool(100).starmap(process_by_ext, file_list)
# Due to text_to_md outputs list of Note_attrs tuples, this should turn write_list to a flat list
flat_write_list = []
for object in write_list:
if type(object) == list:
for item in object:
flat_write_list.append(item)
else:
if type(item) == Note_attrs:
flat_write_list.append(item)
elif type(object) == Note_attrs:
flat_write_list.append(object)
# Create or append existing text files based on Note_attrs tuples data

View file

@ -1,31 +1,30 @@
import QtQml 2.2
import QOwnNotesTypes 1.0
/*
*/
Script {
property string scriptDirPath
property string inboxFolder
property string tagMarker
property string pyBin
function getPyCommand() {
var pyVer = script.startSynchronousProcess('python3', '-V', '').toString()
if (pyVer.indexOf('Python 3') != '-1') {
return 'python3'
}
var pyVer = script.startSynchronousProcess('python', '-V', '').toString()
if (pyVer.indexOf('Python 3') != '-1') {
return 'python'
}
var pyVer = script.startSynchronousProcess('py', '-V', '').toString()
if (pyVer.indexOf('Python 3') != '-1') {
return 'py'
}
function checkPyCommand() {
if (script.startSynchronousProcess('pythonw', '-V', '').toString().indexOf('Python 3') != '-1') {return 'pythonw'}
if (script.startSynchronousProcess('python3', '-V', '').toString().indexOf('Python 3') != '-1') {return 'python3'}
if (script.startSynchronousProcess('python', '-V', '').toString().indexOf('Python 3') != '-1') {return 'python'}
return ''
}
function setDefaultPyCommand() {
if (script.getPersistentVariable('MdNT/pyCommand', '') == '') {
script.setPersistentVariable('MdNT/pyCommand', checkPyCommand())
}
return script.getPersistentVariable('MdNT/pyCommand', '')
}
property string scriptDirPath
property string inboxFolder
property bool scanFolder
property string tagMarker
property string pyCommand
property string pandocCommand
property string pandocVersion
property variant settingsVariables: [
{
'identifier': 'inboxFolder',
@ -35,28 +34,80 @@ Script {
'type': 'string',
'default': 'Inbox',
},
{
'identifier': 'scanFolder',
'name': 'Scan whole folder rather than only Inbox folder',
'description': 'If true the script will convert any non-".md" file in folder to note. \n' +
'"Sub-folder to single note" and modification times in note titles will still be only for Inbox.',
'type': 'boolean',
'default': 'false',
},
{
'identifier': 'tagMarker',
'name': 'Tag word marker',
'description': 'A symbol or group of symbols which start a "tag" word for txt notes. \n' +
'description': 'A symbol or group of symbols which start a "topic" word for ".txt" notes. \n' +
'For example a txt note with "@tag" word will go to "tag.md" note',
'type': 'string',
'default': '@',
},
{
'identifier': 'pyBin',
'identifier': 'pyCommand',
'name': 'Command/path to run Python 3 Interpreter',
'description': "Put a command or path for Python 3 interpreter here.",
'description': "Put a command or path to run Python 3 interpreter.",
'type': 'file',
'default': getPyCommand(),
}
'default': setDefaultPyCommand(),
},
{
'identifier': 'pandocCommand',
'name': 'Command/path to run Pandoc',
'description': "Put a command or path to run Pandoc.",
'type': 'file',
'default': 'pandoc',
},
]
function init() {
if (pyBin == '') {
script.informationMessageBox("Can't find Python 3 interpreter.\n" +
'Please set the correct path to its binary in the script settings.',
'Inbox script')
pandocVersion = script.getPersistentVariable('MdNT/pandocVersion', '')
/// Check if set pyCommand can run Python 3
if (script.getPersistentVariable('MdNT/pyCommand', '') != pyCommand) {
if (script.startSynchronousProcess(pyCommand, '-V', '').toString().indexOf('Python 3') != '-1') {
script.setPersistentVariable('MdNT/pyCommand', pyCommand)
}
else {
script.setPersistentVariable('MdNT/pyCommand', '')
}
}
/// Get the version of pandoc
if (script.getPersistentVariable('MdNT/pandocCommand', '') != pandocCommand) {
var pandocCheck = script.startSynchronousProcess(pandocCommand, '-v', '').toString().split('\n')[0]
if (pandocCheck.indexOf('pandoc') != '-1') {
script.setPersistentVariable('MdNT/pandocCommand', pandocCommand)
script.setPersistentVariable('MdNT/pandocVersion', pandocCheck.slice(7))
pandocVersion = pandocCheck.slice(7)
}
else {
script.setPersistentVariable('MdNT/pandocCommand', '')
}
}
/// Issues alerts
if (script.getPersistentVariable('MdNT/pandocCommand', '') == '') {
script.informationMessageBox('The command/path for pandoc in the script settings is not valid\n' +
'Converting web pages will be disabled.',
'Script')
script.setPersistentVariable('MdNT/pandocCommand', pandocCommand)
script.setPersistentVariable('MdNT/pandocVersion', '')
pandocVersion = ''
}
if (script.getPersistentVariable('MdNT/pyCommand', '') == '') {
script.informationMessageBox('The command/path for Python 3 interpreter in the script settings is not valid\n' +
'Please set the correct command/path.',
'Script')
}
else {
script.registerCustomAction('inbox', 'Process inbox folder', 'Inbox', 'mail-receive.svg')
@ -68,8 +119,22 @@ Script {
var pyScriptPath = scriptDirPath + script.dirSeparator() + 'inbox.py'
var inboxPath = script.currentNoteFolderPath() + script.dirSeparator() + inboxFolder
script.startDetachedProcess(pyBin, [pyScriptPath, inboxPath, script.currentNoteFolderPath(), tagMarker])
var args = [pyScriptPath,
'--inbox', inboxPath,
'--folder', script.currentNoteFolderPath(),
'--marker', tagMarker]
if (scanFolder == true) {
args.push('--scan-folder')
}
if (pandocVersion != '') {
args.push('--pandoc-bin', pandocCommand,
'--pandoc-ver', pandocVersion)
}
script.startDetachedProcess(pyCommand, args)
script.log('Processing inbox...')
}
}
}
}

View file

@ -2,10 +2,12 @@
"name": "Inbox [beta]",
"identifier": "inbox",
"script": "inbox.qml",
"resources": ["inbox.py", "md_link.py", "safe_path.py"],
"resources": ["inbox.py", "md_link.py", "md_convert.py", "safe_path.py"],
"authors": ["@Maboroshy"],
"platforms": ["linux", "macos", "windows"],
"version": "0.0.1",
"version": "0.1.0",
"minAppVersion": "17.05.8",
"description" : "Inbox is a complex script to organize data you put to inbox folder from different devices and applications.\n\n<b>The script alters files in inbox folder you set. It's currently at beta stage, so using it for unrecoverable data is discouraged.</b>\n\n<b>Features:</b>\n- The script turns all .txt files and files with no extension to .md note with modification time in the file name.\n- If there's a word that starts with a '@' (configurable) in file text, content of that file will be added to .md note named like that word. The content of .txt file with '@tag' in text will be added to 'tag.md' file.\n- Every URL in text file will be converted to markdown link, with web page favicon and title if possible.\n- Any image file placed to inbox folder will be moved to media folder. The script will put .md note with the in-line image instead.\n- Any other file placed to inbox folder will be moved to attachments folder. The script will put .md note with a link to the file instead. On Linux the file icon will be put before link.\n- Text/link of file placed to sub-folder of inbox folder will be added to .md note named like the sub-folder. Everything put to 'topic' sub-folder will be added to 'topic.md' file.\n- (Linux only) The script will put thumbnail for .pdf file as a link to it.\n- (Linux only) The script will replace large in-line images with a smaller ones as a link to original ones.\n\n<b>Dependencies:</b>\n- <a href=\"https://www.python.org/downloads/\">Python 3.3+ Interpreter</a>;\n- (Linux only, pdf thumbnails) ghostscript;\n- (Linux only, image thumbnails) imagemagick;\n- (Linux only, file icons) python-gobject.\n\n<b>Usage:</b>\nRun the script by toolbar button or menu item."
"description" : "Inbox is a complex script to organize data added from different devices and applications.\n\n<b>It's currently at beta stage, so using it for unrecoverable data is discouraged.</b>\n\nThe script processes files in inbox folder you set or whole note folder (depending on settings) as follows:\n- The script turns all .txt files and files with no extension to .md note with modification time in the file name.\n- If there's a word that starts with a '@' (configurable) in file text, content of that file will be added to .md note named like that word. The content of .txt file with '@tag' in text will be added to 'tag.md' file.\n- Every URL in text file will be converted to markdown link, with web page favicon and title if possible.\n- Any web page, saved as .htm file with '_files' folder, will be converted to .md note.\n- Any image file placed to inbox folder will be moved to media folder. The script will put .md note with the in-line image instead.\n- Any other file placed to inbox folder will be moved to attachments folder. The script will put .md note with a link to the file instead. On Linux the file icon will be put before link.\n- (Inbox folder only) Text/link of file placed to sub-folder of inbox folder will be added to .md note named like the sub-folder. Everything put to 'topic' sub-folder will be added to 'topic.md' file.\n- (Linux only) The script will put thumbnail for .pdf file as a link to it.\n- (Linux only) The script will replace large in-line images with a smaller ones as a link to original ones.\n\n <a href=\"https://github.com/qownnotes/scripts/blob/master/inbox/workflow.md\">Workflow examples</a>
<b>Dependencies:</b>\n<a href=\"https://www.python.org/downloads/\">Python 3.3+ Interpreter</a>;\n<a href=\"http://pandoc.org/installing.html\">Pandoc</a>;\n(Linux only, pdf thumbnails) ghostscript;\n(Linux only, image thumbnails) imagemagick;\n(Linux only, file icons) python-gobject.\n\n<b>Usage:</b>\nRun the script by toolbar button or menu item."
}

69
inbox/md_convert.py Normal file
View file

@ -0,0 +1,69 @@
import re
import os
import shutil
import subprocess
import urllib.parse
import distutils.version
def html_text(html_text, pandoc_bin='pandoc', pandoc_ver='1.19.1'):
"""
This will convert html_text to markdown by running pandoc_bin and return markdown text
:param html_text: html text to convert
:param pandoc_bin: command/path to run pandoc
:param pandoc_ver: pandoc version as string to use appropriate set of options
:return: converted markdown text
"""
if distutils.version.LooseVersion(pandoc_ver) < distutils.version.LooseVersion('1.16'):
pandoc_args = [pandoc_bin, '-f', 'html', '-t', 'markdown_strict+pipe_tables-raw_html', '--no-wrap']
elif distutils.version.LooseVersion(pandoc_ver) < distutils.version.LooseVersion('1.19'):
pandoc_args = [pandoc_bin, '-f', 'html', '-t', 'markdown_strict+pipe_tables-raw_html', '--wrap=none']
else:
pandoc_args = [pandoc_bin, '-f', 'html', '-t', 'markdown_strict+pipe_tables-raw_html', '--wrap=none',
'--atx-headers']
# Remove firefox reader mode panel if there's one
html_text = re.sub('<ul id="reader-toolbar" class="toolbar">.*</li></ul></ul>', '', html_text, flags=re.DOTALL)
try:
pandoc_pipe = subprocess.Popen(pandoc_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
md_text = pandoc_pipe.communicate(input=html_text.encode('utf-8'))[0].decode('utf-8')
except:
return
return md_text
def saved_html(html_path, folder_dir_path, pandoc_bin='pandoc', pandoc_ver='1.19.1'):
"""
This will convert html_text to markdown by running pandoc_bin and return markdown text
I will also move all in-line images to media directory at folder_dir_path and correct the links accordingly
:param html_path: full absolute path to saved html file to convert, with '_files' dir at the same directory
:param folder_dir_path: full absolute path to directory where 'media' directory is
:param pandoc_bin: command/path to run installed pandoc
:param pandoc_ver: pandoc version to use appropriate set of options
:return:
"""
with open(html_path, 'r') as html:
md_text = html_text(html.read(), pandoc_bin, pandoc_ver)
if not md_text:
return
image_links = re.findall('!\[[^]]*] *\(([^)]*)', md_text) # TODO What if folder name has brackets
for link in image_links:
link_path_tuple = os.path.split(urllib.parse.unquote(link))
file_path = os.path.join(os.path.dirname(html_path), *link_path_tuple)
new_file_path = os.path.join(folder_dir_path, 'media', *link_path_tuple)
new_link_path = 'file://media/' + '/'.join(link_path_tuple)
md_text = md_text.replace(link, new_link_path)
try:
os.makedirs(os.path.dirname(new_file_path), exist_ok=True)
os.rename(file_path, new_file_path)
except OSError:
pass
shutil.rmtree(os.path.splitext(html_path)[0] + '_files', True)
return md_text

View file

@ -5,6 +5,7 @@ import shutil
import hashlib
import platform
import subprocess
import urllib.error
import urllib.request
@ -118,16 +119,16 @@ class File:
if platform.system() == 'Linux':
try:
file = Gio.File.new_for_path(self.path)
except NameError:
file_info = file.query_info('standard::icon', 0, Gio.Cancellable())
file_icon = file_info.get_icon().get_names()[0]
icon_theme = Gtk.IconTheme.get_default()
icon_info = icon_theme.lookup_icon(file_icon, icon_size, 0)
icon_path = icon_info.get_filename()
except (NameError, AttributeError):
return ''
file_info = file.query_info('standard::icon', 0, Gio.Cancellable())
file_icon = file_info.get_icon().get_names()[0]
icon_theme = Gtk.IconTheme.get_default()
icon_info = icon_theme.lookup_icon(file_icon, icon_size, 0)
icon_path = icon_info.get_filename()
if os.path.isfile(icon_path):
if save:
icon_store_path = os.path.join(self.folder_dir_path, 'media', 'fileicons')

31
inbox/workflow.md Normal file
View file

@ -0,0 +1,31 @@
Inbox script workflows
========================
## Saving web pages
1. Open web page in browser;
2. Use "Reader view" to remove the clutter;
3. Save web page to inbox or anywhere inside note folder (if "scan folder" option is on);
4. Web page will be converted to markdown note
These Firefox extensions will make this workflow more efficient:
[Save File to](https://addons.mozilla.org/en/firefox/addon/save-file-to/) by Atte Kemppilä - adds new context menu items to quickly save web page to desired directory.
[Stylish](https://addons.mozilla.org/en/firefox/addon/stylish/?src=search) with [Reader Mode Button Always Visible](https://userstyles.org/styles/123290/reader-mode-button-always-visible) userstyle - forces "Reader view" availability for every page or
[Open in Reader View](https://addons.mozilla.org/en/firefox/addon/reader-view/) by rNeomy - adds context menu items to use "Reader view" even on pages Firefox doesn't allow to.
## Photographed documents
1. Create sub-folder in inbox;
2. Put photos of document pages to that sub-folder;
3. All photos will be in one note named like sub-folder;
4. You can add more pages later.
[Easy Scanner](https://play.google.com/store/apps/details?id=com.easy4u.scanner) by Easy4u is quite good light photo scanner application for Android.
## Dropping attachments
Drop any important non-note file, like a .pdf, or .docx to inbox or anywhere inside note folder (if "scan folder" option is on) to have a note linking to that file in QOwnNotes. Attachments allow your notes to extend the limits of markdown when it's needed.
## Note-taking and bookmarking from Android
1. Make a folder on your Android device synced with inbox;
2. Open web page or anything you want to note - sms, message, text selection, QR code;
3. "Share" it with plain text editor like [Fast Notepad](https://play.google.com/store/apps/details?id=com.taxaly.noteme.v2&hl=en), with its save folder synced with your inbox;
4. Add @topic to text to save it to "topic" note;
5. You can also share images and other files with file manager like [Total commander](https://play.google.com/store/apps/details?id=com.ghisler.android.TotalCommander) to drop them to folder synced with your inbox.