Add files

This commit is contained in:
Benjamin Jacob Reji
2021-01-10 03:03:06 +04:00
parent 40e255c71a
commit f67a942acd
33 changed files with 724 additions and 0 deletions

145
.gitignore vendored Normal file
View File

@@ -0,0 +1,145 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# VSCode
.vscode/*
*.code-workspace
# Local History for Visual Studio Code
.history/%

5
_headers Normal file
View File

@@ -0,0 +1,5 @@
/*
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: content-type

150
build.py Normal file
View File

@@ -0,0 +1,150 @@
# coding: utf-8
'''
Parse extensions/*.toml files, output a static site with following structure:
public/
|-my-extension-1/
| |-index.json <- extension info
| |-index.html <- extension entrance (component)
| |-dist <- extension resources
| |-... <- other files
|
|-index.json <- repo info, contain all extensions' info
'''
import glob
import json
import os
import shutil
from subprocess import run
import toml
def onerror(func, path, exc_info):
"""
Error handler for ``shutil.rmtree``.
If the error is due to an access error (read only file)
it attempts to add write permission and then retries.
If the error is for another reason it re-raises the error.
Usage : ``shutil.rmtree(path, onerror=onerror)``
"""
import stat
if not os.access(path, os.W_OK):
# Is the error an access error ?
os.chmod(path, stat.S_IWUSR)
func(path)
else:
raise
def main(base_url):
while base_url.endswith('/'):
base_url = base_url[:-1]
print('Fetching extensions...')
base_dir = os.path.dirname(os.path.abspath(__file__))
extension_dir = os.path.join(base_dir, 'extensions')
public_dir = os.path.join(base_dir, 'public')
if os.path.exists(public_dir):
shutil.rmtree(public_dir, ignore_errors=False, onerror=onerror)
os.makedirs(public_dir)
os.chdir(public_dir)
extensions = []
# Read and parse all extension info
for fname in os.listdir(extension_dir):
if not fname.endswith('.toml'):
continue
with open(os.path.join(extension_dir, fname)) as rf:
ext = toml.load(rf)
# Build extension info
repo_name = ext['github'].split('/')[-1]
# https://example.com/my-extension/index.html
extension_url = '/'.join([base_url, repo_name, ext['main']])
# https://example.com/my-extension/index.json
extension_info_url = '/'.join([base_url, repo_name, 'index.json'])
extension = dict(
identifier=ext['id'],
name=ext['name'],
content_type=ext['content_type'],
area=ext.get('area', None),
version=ext['version'],
description=ext.get('description', None),
marketing_url=ext.get('marketing_url', None),
thumbnail_url=ext.get('thumbnail_url', None),
valid_until='2030-05-16T18:35:33.000Z',
url=extension_url,
download_url='https://github.com/{github}/archive/{version}.zip'.format(**ext),
latest_url=extension_info_url,
flags=ext.get('flags', []),
dock_icon=ext.get('dock_icon', {}),
layerable=ext.get('layerable', None),
deletion_warning=ext.get('deletion_warning', None),
)
# mfa-link (2FA manager) has no expiration
if ext.get('no_expire', False):
extension.pop('valid_until', None)
# Strip empty values
extension = {k: v for k, v in extension.items() if v}
# That is very strange, StandardNotes does not upload some npm packages
# when extensions get updated. We'll have to handle them by git.
# git clone --branch {version} --depth 1 {github_url}
run([
'git', '-c', 'advice.detachedHead=false',
'clone',
'--branch', ext['version'],
'--depth', '1',
'https://github.com/{github}.git'.format(**ext),
])
shutil.rmtree(os.path.join(public_dir, repo_name, '.git'), ignore_errors=False, onerror=onerror)
# Generate JSON file for each extension
with open(os.path.join(public_dir, repo_name, 'index.json'), 'w') as wf:
json.dump(extension, wf)
extensions.append(extension)
print('Loaded extension: {}'.format(ext['name']))
os.chdir('..')
# Generate the index JSON file
with open(os.path.join(public_dir, 'index.json'), 'w') as wf:
json.dump(
dict(
content_type='SN|Repo',
valid_until='2030-05-16T18:35:33.000Z',
packages=extensions,
),
wf,
)
# Cleanup unnecessary files
print('Cleaning up...')
for filename in ['README.md', 'Gruntfile.js', '.babelrc', 'package.json', '*.map', 'src', 'vendor']:
for matched_fpath in glob.glob(f'public/*/{filename}'):
if os.path.isdir(matched_fpath):
shutil.rmtree(matched_fpath, ignore_errors=False, onerror=onerror)
elif os.path.isfile(matched_fpath):
os.remove(matched_fpath)
else:
continue
print(f'> Removed {matched_fpath}')
# Distribute header rules for Netlify
shutil.copyfile('_headers', os.path.join(public_dir, '_headers'))
print('Build finished')
if __name__ == '__main__':
main(os.getenv('URL', 'https://standardnotes-extensions.netlify.app/'))

View File

@@ -0,0 +1,12 @@
id = "org.standardnotes.action-bar"
npm = "sn-action-bar"
github = "standardnotes/action-bar"
main = "index.html"
name = "Action Bar"
content_type = "SN|Component"
area = "editor-stack"
version = "1.3.1"
marketing_url = "https://standardnotes.org/extensions/action-bar"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/components/action-bar.jpg"
description = "Useful utility bar with information about the current note as well as actions like duplicate, copy, and save."
flags = []

View File

@@ -0,0 +1,12 @@
id = "org.standardnotes.advanced-markdown-editor"
npm = "sn-advanced-markdown-editor"
github = "standardnotes/markdown-pro"
main = "index.html"
name = "Advanced Markdown Editor"
content_type = "SN|Component"
area = "editor-editor"
version = "1.3.9"
marketing_url = "https://standardnotes.org/extensions/advanced-markdown"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/editors/adv-markdown.jpg"
description = "A fully featured Markdown editor that supports live preview, a styling toolbar, and split pane support."
flags = []

View File

@@ -0,0 +1,16 @@
id = "org.standardnotes.theme-autobiography"
npm = "sn-theme-autobiography"
github = "sn-extensions/autobiography-theme"
main = "dist/dist.css"
name = "Autobiography"
content_type = "SN|Theme"
version = "1.0.0"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/themes/autobiography.jpg"
description = "A theme for writers and readers."
[dock_icon]
type = "circle"
background_color = "#9D7441"
foreground_color = "#ECE4DB"
border_color = "#9D7441"

View File

@@ -0,0 +1,12 @@
id = "org.standardnotes.autocomplete-tags"
npm = "sn-autocomplete-tags"
github = "sn-extensions/autocomplete-tags"
main = "index.html"
name = "Quick Tags"
content_type = "SN|Component"
area = "note-tags"
version = "1.3.2"
marketing_url = ""
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/components/autocomplete.jpg"
description = "Work more efficiently by quickly selecting from a live list of tags while you type. Supports keyboard shortcuts and folders."
flags = []

View File

@@ -0,0 +1,12 @@
id = "org.standardnotes.bold-editor"
npm = "sn-bold-editor"
github = "standardnotes/bold-editor"
main = "dist/index.html"
name = "Bold Editor"
content_type = "SN|Component"
area = "editor-editor"
version = "1.1.0"
marketing_url = ""
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/editors/bold.jpg"
description = "A simple and peaceful rich editor that helps you write and think clearly. Features FileSafe integration, so you can embed your encrypted images, videos, and audio recordings directly inline."
flags = []

View File

@@ -0,0 +1,11 @@
id = "org.standardnotes.code-editor"
npm = "sn-code-editor"
github = "standardnotes/code-editor"
main = "index.html"
name = "Code Editor"
content_type = "SN|Component"
area = "editor-editor"
version = "1.3.5"
marketing_url = "https://standardnotes.org/extensions/code-editor"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/editors/code.jpg"
description = "Syntax highlighting and convenient keyboard shortcuts for over 120 programming languages. Ideal for code snippets and procedures."

View File

@@ -0,0 +1,12 @@
id = "org.standardnotes.theme-dynamic"
npm = "sn-theme-dynamic"
github = "sn-extensions/dynamic-theme"
main = "dist/dist.css"
name = "Dynamic"
layerable = true
content_type = "SN|Theme"
version = "1.0.0"
marketing_url = "https://standardnotes.org/extensions/dynamic"
description = "A smart theme that minimizes the tags and notes panels when they are not in use."

View File

@@ -0,0 +1,11 @@
id = "org.standardnotes.fancy-markdown-editor"
npm = "sn-fancy-markdown-editor"
github = "sn-extensions/math-editor"
main = "index.html"
name = "Math Editor"
content_type = "SN|Component"
area = "editor-editor"
version = "1.3.4"
marketing_url = "https://standardnotes.org/extensions/math-editor"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/editors/fancy-markdown.jpg"
description = "A beautiful split-pane Markdown editor with synced-scroll and LaTeX support. When LaTeX is detected, makes external render network request."

13
extensions/file-safe.toml Normal file
View File

@@ -0,0 +1,13 @@
id = "org.standardnotes.file-safe"
npm = "sn-filesafe"
github = "standardnotes/filesafe-bar"
main = "dist/index.html"
name = "FileSafe"
content_type = "SN|Component"
area = "editor-stack"
version = "2.0.10"
marketing_url = "https://standardnotes.org/extensions/filesafe"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/FileSafe-banner.png"
description = "Encrypted attachments for your notes using your Dropbox, Google Drive, or WebDAV server. Limited to 50MB per file."
flags = []

View File

@@ -0,0 +1,17 @@
id = "org.standardnotes.theme-focus"
npm = "sn-theme-focus"
github = "sn-extensions/focus-theme"
main = "dist/dist.css"
name = "Focus"
content_type = "SN|Theme"
version = "1.2.3"
marketing_url = "https://standardnotes.org/extensions/focused"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/themes/focus-with-mobile.jpg"
description = "For when you need to go in."
[dock_icon]
type = "circle"
background_color = "#a464c2"
foreground_color = "#ffffff"
border_color = "#a464c2"

View File

@@ -0,0 +1,12 @@
id = "org.standardnotes.folders-component"
npm = "sn-folders-component"
github = "standardnotes/folders-component"
main = "index.html"
name = "Folders Component"
content_type = "SN|Component"
area = "tags-list"
version = "1.3.5"
marketing_url = ""
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/components/folders.jpg"
description = "Create nested folders from your tags with easy drag and drop. Folders also supports Smart Tags, which allow you to build custom filters for viewing your notes."
flags = []

View File

@@ -0,0 +1,17 @@
id = "org.standardnotes.theme-futura"
npm = "sn-futura-theme"
github = "sn-extensions/futura-theme"
main = "dist/dist.css"
name = "Futura"
content_type = "SN|Theme"
version = "1.2.2"
marketing_url = "https://standardnotes.org/extensions/futura"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/themes/futura-with-mobile.jpg"
description = "Calm and relaxed. Take some time off."
[dock_icon]
type = "circle"
background_color = "#fca429"
foreground_color = "#ffffff"
border_color = "#fca429"

View File

@@ -0,0 +1,13 @@
id = "org.standardnotes.github-push"
npm = "sn-github-push"
github = "sn-extensions/github-push"
main = "index.html"
name = "GitHub Push"
content_type = "SN|Component"
area = "editor-stack"
version = "1.2.1"
marketing_url = "https://standardnotes.org/extensions/github-push"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/components/github-push.jpg"
description = "Push note changes to a public or private GitHub repository, with options for file extension and commit message."

11
extensions/mfa-link.toml Normal file
View File

@@ -0,0 +1,11 @@
id = "org.standardnotes.mfa-link"
npm = "sn-mfa-link"
github = "sn-extensions/mfa-link"
main = "dist/index.html"
name = "2FA Manager"
content_type = "SN|Component"
area = "modal"
version = "1.2.2"
description = "Configure two-factor authentication to add an extra level of security to your account."
no_expire = true
deletion_warning = "Deleting 2FA Manager will not disable 2FA from your account. To disable 2FA, first open 2FA Manager, then follow the prompts."

View File

@@ -0,0 +1,17 @@
id = "org.standardnotes.theme-midnight"
npm = "sn-theme-midnight"
github = "sn-extensions/midnight-theme"
main = "dist/dist.css"
name = "Midnight"
content_type = "SN|Theme"
version = "1.2.1"
marketing_url = "https://standardnotes.org/extensions/midnight"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/themes/midnight-with-mobile.jpg"
description = "Elegant utilitarianism."
[dock_icon]
type = "circle"
background_color = "#086DD6"
foreground_color = "#ffffff"
border_color = "#086DD6"

View File

@@ -0,0 +1,12 @@
id = "org.standardnotes.minimal-markdown-editor"
npm = "sn-minimal-markdown-editor"
github = "sn-extensions/minimal-markdown-editor"
main = "index.html"
name = "Minimal Markdown Editor"
content_type = "SN|Component"
area = "editor-editor"
version = "1.3.5"
marketing_url = "https://standardnotes.org/extensions/minimal-markdown-editor"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/editors/min-markdown.jpg"
description = "A minimal Markdown editor with live rendering and in-text search via Ctrl/Cmd + F"
flags = []

View File

@@ -0,0 +1,15 @@
id = "org.standardnotes.theme-no-distraction"
npm = "sn-theme-no-distraction"
github = "sn-extensions/no-distraction-theme"
main = "dist/dist.css"
name = "No Distraction"
content_type = "SN|Theme"
version = "1.2.2"
layerable = true
marketing_url = "https://standardnotes.org/extensions/no-distraction"
description = "A theme for focusing on your writing."
[dock_icon]
type = "svg"
source = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><path d=\"M424 64H88c-26.6 0-48 21.6-48 48v288c0 26.4 21.4 48 48 48h336c26.4 0 48-21.6 48-48V112c0-26.4-21.4-48-48-48zm0 336H88V176h336v224z\"/></svg>"

View File

@@ -0,0 +1,17 @@
id = "com.ceiphr.overcast"
github = "ceiphr/sn-overcast-theme"
main = "dist/dist.css"
name = "Overcast"
content_type = "SN|Theme"
version = "1.1.2"
marketing_url = "https://github.com/ceiphr/sn-overcast-theme"
thumbnail_url = "https://i.imgur.com/KDnyBGx.png"
description = "Overcast to become cloudy or dark — just like this theme."
[dock_icon]
type = "circle"
background_color = "#039be5"
foreground_color = "#19181a"
border_color = "#039be5"

View File

@@ -0,0 +1,12 @@
id = "org.standardnotes.plus-editor"
npm = "sn-plus-editor"
github = "standardnotes/plus-editor"
main = "index.html"
name = "Plus Editor"
content_type = "SN|Component"
area = "editor-editor"
version = "1.4.3"
marketing_url = "https://standardnotes.org/extensions/plus-editor"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/editors/plus-editor.jpg"
description = "From highlighting to custom font sizes and colors, to tables and lists, this editor is perfect for crafting any document."
flags = []

View File

@@ -0,0 +1,12 @@
id = "org.standardnotes.standard-sheets"
npm = "sn-spreadsheets"
github = "standardnotes/secure-spreadsheets"
main = "dist/index.html"
name = "Secure Spreadsheets"
content_type = "SN|Component"
area = "editor-editor"
version = "1.3.4"
marketing_url = ""
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/editors/spreadsheets.png"
description = "A powerful spreadsheet editor with formatting and formula support. Not recommended for large data sets, as encryption of such data may decrease editor performance."
flags = []

View File

@@ -0,0 +1,13 @@
id = "org.standardnotes.simple-markdown-editor"
npm = "sn-simple-markdown-editor"
github = "standardnotes/markdown-basic"
main = "dist/index.html"
name = "Simple Markdown Editor"
content_type = "SN|Component"
area = "editor-editor"
version = "1.3.6"
marketing_url = "https://standardnotes.org/extensions/simple-markdown-editor"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/editors/simple-markdown.jpg"
description = "A Markdown editor with dynamic split-pane preview."
flags = []

View File

@@ -0,0 +1,12 @@
id = "org.standardnotes.simple-task-editor"
npm = "sn-simple-task-editor"
github = "sn-extensions/simple-task-editor"
main = "dist/index.html"
name = "Simple Task Editor"
content_type = "SN|Component"
area = "editor-editor"
version = "1.3.5"
marketing_url = "https://standardnotes.org/extensions/simple-task-editor"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/editors/task-editor.jpg"
description = "A great way to manage short-term and long-term to-do's. You can mark tasks as completed, change their order, and edit the text naturally in place."
flags = []

View File

@@ -0,0 +1,17 @@
id = "org.standardnotes.theme-solarized-dark"
npm = "sn-theme-solarized-dark"
github = "sn-extensions/solarized-dark-theme"
main = "dist/dist.css"
name = "Solarized Dark"
content_type = "SN|Theme"
version = "1.2.1"
marketing_url = "https://standardnotes.org/extensions/solarized-dark"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/themes/solarized-dark.jpg"
description = "The perfect theme for any time."
[dock_icon]
type = "circle"
background_color = "#2AA198"
foreground_color = "#ffffff"
border_color = "#2AA198"

View File

@@ -0,0 +1,17 @@
id = "org.standardnotes.theme-titanium"
npm = "sn-theme-titanium"
github = "sn-extensions/titanium-theme"
main = "dist/dist.css"
name = "Titanium"
content_type = "SN|Theme"
version = "1.2.2"
marketing_url = "https://standardnotes.org/extensions/titanium"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/themes/titanium-with-mobile.jpg"
description = "Light on the eyes, heavy on the spirit."
[dock_icon]
type = "circle"
background_color = "#6e2b9e"
foreground_color = "#ffffff"
border_color = "#6e2b9e"

View File

@@ -0,0 +1,11 @@
id = "org.standardnotes.token-vault"
npm = "sn-token-vault"
github = "sn-extensions/token-vault"
main = "dist/index.html"
name = "TokenVault"
content_type = "SN|Component"
area = "editor-editor"
version = "1.0.5"
thumbnail_url = "https://standard-notes.s3.amazonaws.com/screenshots/models/editors/token-vault.png"
description = "Encrypt and protect your 2FA secrets for all your internet accounts. TokenVault handles your 2FA secrets so that you never lose them again, or have to start over when you get a new device."
flags = [ "Beta",]

View File

@@ -0,0 +1,13 @@
id = "org.standardnotes.vim-editor"
npm = "sn-vim-editor"
github = "sn-extensions/vim-editor"
main = "index.html"
name = "Vim Editor"
content_type = "SN|Component"
area = "editor-editor"
version = "1.3.2"
marketing_url = "https://standardnotes.org/extensions/vim-editor"
thumbnail_url = "https://s3.amazonaws.com/standard-notes/screenshots/models/editors/vim.jpg"
description = "A code editor with Vim key bindings."
flags = []

3
netlify.toml Normal file
View File

@@ -0,0 +1,3 @@
[build]
command = "python build.py"
publish = "public"

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
toml==0.10.2
requests==2.25.1

1
runtime.txt Normal file
View File

@@ -0,0 +1 @@
3.7

69
update.py Normal file
View File

@@ -0,0 +1,69 @@
# coding: utf-8
'''
Update all extensions to their latest release.
This script only updates version numbers in .toml files,
please remember to build again after the update.
Caution: this script uses unauthenticated requests to call GitHub API,
so it is limited by the same rate limit as unauthenticated users.
Please don't run this script repeatedly in a short period.
See https://developer.github.com/v3/#rate-limiting for more details.
'''
import collections
from http import HTTPStatus as hstatus
import json
import os
import requests
import toml
def get_latest_version(repo):
resp = requests.get(
f'https://api.github.com/repos/{repo}/releases/latest',
headers={
'user-agent': 'snextensions-updater',
},
)
if resp.status_code != hstatus.OK:
raise Exception(f'Unexpected HTTP status: {resp.status_code}')
return resp.json()['tag_name']
def main():
base_dir = os.path.dirname(os.path.abspath(__file__))
extension_dir = os.path.join(base_dir, 'extensions')
# Read and parse all extension info
for fname in os.listdir(extension_dir):
if not fname.endswith('.toml'):
continue
with open(os.path.join(extension_dir, fname)) as rf:
# Notice the `_dict`, this is to retain key order,
# avoiding unnecessary diffs between commits
ext = toml.load(rf, _dict=collections.OrderedDict)
repo_name = ext['github']
version = ext['version']
try:
latest_version = get_latest_version(repo_name)
except Exception as e:
print(f'{repo_name} : Failed <{e}>')
continue
else:
if latest_version == version:
print(f'🟢 {repo_name} : {version} is already the latest version')
continue
with open(os.path.join(extension_dir, fname), 'w') as wf:
ext['version'] = latest_version
toml.dump(ext, wf)
print(f'{repo_name} : {version} => {latest_version}')
if __name__ == '__main__':
main()