From 13026876f82edabc57019ac75f208c0c479947d5 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Sun, 6 Jul 2025 19:40:34 +0200 Subject: [PATCH 01/22] Add initial version of Markdown Notes feature. --- app.py | 468 +++++++++++++++++++++- migrate_db.py | 157 ++++++++ models.py | 178 ++++++++- requirements.txt | 1 + static/js/ace/ace.js | 23 ++ static/js/ace/ext-language_tools.js | 8 + static/js/ace/mode-markdown.js | 8 + static/js/ace/theme-github.js | 8 + static/js/ace/theme-monokai.js | 8 + templates/layout.html | 1 + templates/note_editor.html | 600 ++++++++++++++++++++++++++++ templates/note_view.html | 566 ++++++++++++++++++++++++++ templates/notes_list.html | 371 +++++++++++++++++ 13 files changed, 2395 insertions(+), 2 deletions(-) create mode 100644 static/js/ace/ace.js create mode 100644 static/js/ace/ext-language_tools.js create mode 100644 static/js/ace/mode-markdown.js create mode 100644 static/js/ace/theme-github.js create mode 100644 static/js/ace/theme-monokai.js create mode 100644 templates/note_editor.html create mode 100644 templates/note_view.html create mode 100644 templates/notes_list.html diff --git a/app.py b/app.py index cbb5013..decaa49 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,5 @@ from flask import Flask, render_template, request, redirect, url_for, jsonify, flash, session, g, Response, send_file, abort -from models import db, TimeEntry, WorkConfig, User, SystemSettings, Team, Role, Project, Company, CompanyWorkConfig, CompanySettings, UserPreferences, WorkRegion, AccountType, ProjectCategory, Task, SubTask, TaskStatus, TaskPriority, TaskDependency, Sprint, SprintStatus, Announcement, SystemEvent, WidgetType, UserDashboard, DashboardWidget, WidgetTemplate, Comment, CommentVisibility, BrandingSettings +from models import db, TimeEntry, WorkConfig, User, SystemSettings, Team, Role, Project, Company, CompanyWorkConfig, CompanySettings, UserPreferences, WorkRegion, AccountType, ProjectCategory, Task, SubTask, TaskStatus, TaskPriority, TaskDependency, Sprint, SprintStatus, Announcement, SystemEvent, WidgetType, UserDashboard, DashboardWidget, WidgetTemplate, Comment, CommentVisibility, BrandingSettings, Note, NoteLink, NoteVisibility from data_formatting import ( format_duration, prepare_export_data, prepare_team_hours_export_data, format_table_data, format_graph_data, format_team_data, format_burndown_data @@ -1606,6 +1606,404 @@ def contact(): # redacted return render_template('contact.html', title='Contact') + +# Notes Management Routes +@app.route('/notes') +@login_required +@company_required +def notes_list(): + """List all notes accessible to the user""" + # Get filter parameters + visibility_filter = request.args.get('visibility', 'all') + tag_filter = request.args.get('tag') + search_query = request.args.get('search', request.args.get('q')) + + # Base query - all notes in user's company + query = Note.query.filter_by(company_id=g.user.company_id, is_archived=False) + + # Apply visibility filter + if visibility_filter == 'private': + query = query.filter_by(created_by_id=g.user.id, visibility=NoteVisibility.PRIVATE) + elif visibility_filter == 'team': + query = query.filter_by(visibility=NoteVisibility.TEAM) + if g.user.role not in [Role.ADMIN, Role.SYSTEM_ADMIN]: + query = query.filter_by(team_id=g.user.team_id) + elif visibility_filter == 'company': + query = query.filter_by(visibility=NoteVisibility.COMPANY) + else: # 'all' - show all accessible notes + # Complex filter for visibility + from sqlalchemy import or_, and_ + conditions = [ + # User's own notes + Note.created_by_id == g.user.id, + # Company-wide notes + Note.visibility == NoteVisibility.COMPANY, + # Team notes if user is in the team + and_( + Note.visibility == NoteVisibility.TEAM, + Note.team_id == g.user.team_id + ) + ] + # Admins can see all team notes + if g.user.role in [Role.ADMIN, Role.SYSTEM_ADMIN]: + conditions.append(Note.visibility == NoteVisibility.TEAM) + + query = query.filter(or_(*conditions)) + + # Apply tag filter + if tag_filter: + query = query.filter(Note.tags.like(f'%{tag_filter}%')) + + # Apply search + if search_query: + query = query.filter( + db.or_( + Note.title.ilike(f'%{search_query}%'), + Note.content.ilike(f'%{search_query}%'), + Note.tags.ilike(f'%{search_query}%') + ) + ) + + # Order by pinned first, then by updated date + notes = query.order_by(Note.is_pinned.desc(), Note.updated_at.desc()).all() + + # Get all unique tags for filter dropdown + all_tags = set() + for note in Note.query.filter_by(company_id=g.user.company_id, is_archived=False).all(): + all_tags.update(note.get_tags_list()) + + # Get projects for filter + projects = Project.query.filter_by(company_id=g.user.company_id, is_active=True).all() + + return render_template('notes_list.html', + title='Notes', + notes=notes, + visibility_filter=visibility_filter, + tag_filter=tag_filter, + search_query=search_query, + all_tags=sorted(list(all_tags)), + projects=projects, + NoteVisibility=NoteVisibility) + + +@app.route('/notes/new', methods=['GET', 'POST']) +@login_required +@company_required +def create_note(): + """Create a new note""" + if request.method == 'POST': + title = request.form.get('title', '').strip() + content = request.form.get('content', '').strip() + visibility = request.form.get('visibility', 'Private') + tags = request.form.get('tags', '').strip() + project_id = request.form.get('project_id') + task_id = request.form.get('task_id') + + # Validate + if not title: + flash('Title is required', 'error') + return redirect(url_for('create_note')) + + if not content: + flash('Content is required', 'error') + return redirect(url_for('create_note')) + + try: + # Parse tags + tag_list = [] + if tags: + tag_list = [tag.strip() for tag in tags.split(',') if tag.strip()] + + # Create note + note = Note( + title=title, + content=content, + visibility=NoteVisibility[visibility.upper()], # Convert to uppercase for enum access + tags=','.join(tag_list) if tag_list else None, + created_by_id=g.user.id, + company_id=g.user.company_id + ) + + # Set team_id if visibility is Team + if visibility == 'Team' and g.user.team_id: + note.team_id = g.user.team_id + + # Set optional associations + if project_id: + project = Project.query.filter_by(id=project_id, company_id=g.user.company_id).first() + if project: + note.project_id = project.id + + if task_id: + task = Task.query.filter_by(id=task_id).first() + if task and task.project.company_id == g.user.company_id: + note.task_id = task.id + + # Generate slug + note.slug = note.generate_slug() + + db.session.add(note) + db.session.commit() + + flash('Note created successfully', 'success') + return redirect(url_for('view_note', slug=note.slug)) + + except Exception as e: + db.session.rollback() + logger.error(f"Error creating note: {str(e)}") + flash('Error creating note', 'error') + return redirect(url_for('create_note')) + + # GET request - show form + projects = Project.query.filter_by(company_id=g.user.company_id, is_active=True).all() + tasks = [] + + return render_template('note_editor.html', + title='New Note', + note=None, + projects=projects, + tasks=tasks, + NoteVisibility=NoteVisibility) + + +@app.route('/notes/') +@login_required +@company_required +def view_note(slug): + """View a specific note""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_view(g.user): + abort(403) + + # Get linked notes + outgoing_links = [] + incoming_links = [] + + for link in note.outgoing_links: + if link.target_note.can_user_view(g.user): + outgoing_links.append(link) + + for link in note.incoming_links: + if link.source_note.can_user_view(g.user): + incoming_links.append(link) + + # Get linkable notes for the modal + linkable_notes = [] + if note.can_user_edit(g.user): + # Get all notes the user can view + all_notes = Note.query.filter_by(company_id=g.user.company_id, is_archived=False).all() + for n in all_notes: + if n.id != note.id and n.can_user_view(g.user): + # Check if not already linked + already_linked = any(link.target_note_id == n.id for link in note.outgoing_links) + already_linked = already_linked or any(link.source_note_id == n.id for link in note.incoming_links) + if not already_linked: + linkable_notes.append(n) + + return render_template('note_view.html', + title=note.title, + note=note, + outgoing_links=outgoing_links, + incoming_links=incoming_links, + linkable_notes=linkable_notes, + can_edit=note.can_user_edit(g.user)) + + +@app.route('/notes//edit', methods=['GET', 'POST']) +@login_required +@company_required +def edit_note(slug): + """Edit an existing note""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_edit(g.user): + abort(403) + + if request.method == 'POST': + title = request.form.get('title', '').strip() + content = request.form.get('content', '').strip() + visibility = request.form.get('visibility', 'Private') + tags = request.form.get('tags', '').strip() + project_id = request.form.get('project_id') + task_id = request.form.get('task_id') + + # Validate + if not title: + flash('Title is required', 'error') + return redirect(url_for('edit_note', slug=slug)) + + if not content: + flash('Content is required', 'error') + return redirect(url_for('edit_note', slug=slug)) + + try: + # Parse tags + tag_list = [] + if tags: + tag_list = [tag.strip() for tag in tags.split(',') if tag.strip()] + + # Update note + note.title = title + note.content = content + note.visibility = NoteVisibility[visibility.upper()] # Convert to uppercase for enum access + note.tags = ','.join(tag_list) if tag_list else None + + # Update team_id if visibility is Team + if visibility == 'Team' and g.user.team_id: + note.team_id = g.user.team_id + else: + note.team_id = None + + # Update optional associations + if project_id: + project = Project.query.filter_by(id=project_id, company_id=g.user.company_id).first() + note.project_id = project.id if project else None + else: + note.project_id = None + + if task_id: + task = Task.query.filter_by(id=task_id).first() + if task and task.project.company_id == g.user.company_id: + note.task_id = task.id + else: + note.task_id = None + else: + note.task_id = None + + # Regenerate slug if title changed + new_slug = note.generate_slug() + if new_slug != note.slug: + note.slug = new_slug + + db.session.commit() + + flash('Note updated successfully', 'success') + return redirect(url_for('view_note', slug=note.slug)) + + except Exception as e: + db.session.rollback() + logger.error(f"Error updating note: {str(e)}") + flash('Error updating note', 'error') + return redirect(url_for('edit_note', slug=slug)) + + # GET request - show form + projects = Project.query.filter_by(company_id=g.user.company_id, is_active=True).all() + tasks = [] + if note.project_id: + tasks = Task.query.filter_by(project_id=note.project_id).all() + + return render_template('note_editor.html', + title=f'Edit: {note.title}', + note=note, + projects=projects, + tasks=tasks, + NoteVisibility=NoteVisibility) + + +@app.route('/notes//delete', methods=['POST']) +@login_required +@company_required +def delete_note(slug): + """Delete (archive) a note""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_edit(g.user): + abort(403) + + try: + # Soft delete + note.is_archived = True + note.archived_at = datetime.now() + db.session.commit() + + flash('Note deleted successfully', 'success') + return redirect(url_for('notes_list')) + + except Exception as e: + db.session.rollback() + logger.error(f"Error deleting note: {str(e)}") + flash('Error deleting note', 'error') + return redirect(url_for('view_note', slug=slug)) + + +@app.route('/api/notes//link', methods=['POST']) +@login_required +@company_required +def link_notes(note_id): + """Create a link between two notes""" + source_note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not source_note.can_user_edit(g.user): + return jsonify({'success': False, 'message': 'Permission denied'}), 403 + + data = request.get_json() + target_note_id = data.get('target_note_id') + link_type = data.get('link_type', 'related') + + if not target_note_id: + return jsonify({'success': False, 'message': 'Target note ID required'}), 400 + + target_note = Note.query.filter_by(id=target_note_id, company_id=g.user.company_id).first() + if not target_note: + return jsonify({'success': False, 'message': 'Target note not found'}), 404 + + if not target_note.can_user_view(g.user): + return jsonify({'success': False, 'message': 'Cannot link to this note'}), 403 + + try: + # Check if link already exists (in either direction) + existing_link = NoteLink.query.filter( + db.or_( + db.and_( + NoteLink.source_note_id == note_id, + NoteLink.target_note_id == target_note_id + ), + db.and_( + NoteLink.source_note_id == target_note_id, + NoteLink.target_note_id == note_id + ) + ) + ).first() + + if existing_link: + return jsonify({'success': False, 'message': 'Link already exists between these notes'}), 400 + + # Create link + link = NoteLink( + source_note_id=note_id, + target_note_id=target_note_id, + link_type=link_type, + created_by_id=g.user.id + ) + + db.session.add(link) + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Notes linked successfully', + 'link': { + 'id': link.id, + 'source_note_id': link.source_note_id, + 'target_note_id': link.target_note_id, + 'target_note': { + 'id': target_note.id, + 'title': target_note.title, + 'slug': target_note.slug + } + } + }) + + except Exception as e: + db.session.rollback() + logger.error(f"Error linking notes: {str(e)}") + return jsonify({'success': False, 'message': f'Error linking notes: {str(e)}'}), 500 + # We can keep this route as a redirect to home for backward compatibility @app.route('/timetrack') @login_required @@ -5941,6 +6339,74 @@ def search_sprints(): logger.error(f"Error in search_sprints: {str(e)}") return jsonify({'success': False, 'message': str(e)}) +# Markdown rendering API +@app.route('/api/render-markdown', methods=['POST']) +@login_required +def render_markdown(): + """Render markdown content to HTML for preview""" + try: + data = request.get_json() + content = data.get('content', '') + + if not content: + return jsonify({'html': ''}) + + # Import markdown here to avoid issues if not installed + import markdown + html = markdown.markdown(content, extensions=['extra', 'codehilite', 'toc']) + + return jsonify({'html': html}) + + except Exception as e: + logger.error(f"Error rendering markdown: {str(e)}") + return jsonify({'html': '

Error rendering markdown

'}) + +# Note link deletion endpoint +@app.route('/api/notes//link', methods=['DELETE']) +@login_required +@company_required +def unlink_notes(note_id): + """Remove a link between two notes""" + try: + note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first() + + if not note: + return jsonify({'success': False, 'message': 'Note not found'}) + + if not note.can_user_edit(g.user): + return jsonify({'success': False, 'message': 'Permission denied'}) + + data = request.get_json() + target_note_id = data.get('target_note_id') + + if not target_note_id: + return jsonify({'success': False, 'message': 'Target note ID required'}) + + # Find and remove the link + link = NoteLink.query.filter_by( + source_note_id=note_id, + target_note_id=target_note_id + ).first() + + if not link: + # Try reverse direction + link = NoteLink.query.filter_by( + source_note_id=target_note_id, + target_note_id=note_id + ).first() + + if link: + db.session.delete(link) + db.session.commit() + return jsonify({'success': True, 'message': 'Link removed successfully'}) + else: + return jsonify({'success': False, 'message': 'Link not found'}) + + except Exception as e: + db.session.rollback() + logger.error(f"Error removing note link: {str(e)}") + return jsonify({'success': False, 'message': str(e)}) + if __name__ == '__main__': port = int(os.environ.get('PORT', 5000)) app.run(debug=True, host='0.0.0.0', port=port) \ No newline at end of file diff --git a/migrate_db.py b/migrate_db.py index b98cd2c..bc0728c 100644 --- a/migrate_db.py +++ b/migrate_db.py @@ -76,6 +76,7 @@ def run_all_migrations(db_path=None): migrate_system_events(db_path) migrate_dashboard_system(db_path) migrate_comment_system(db_path) + migrate_notes_system(db_path) # Run PostgreSQL-specific migrations if applicable if FLASK_AVAILABLE: @@ -1272,6 +1273,74 @@ def migrate_postgresql_schema(): """)) db.session.commit() + # Check if note table exists + result = db.session.execute(text(""" + SELECT table_name + FROM information_schema.tables + WHERE table_name = 'note' + """)) + + if not result.fetchone(): + print("Creating note and note_link tables...") + + # Create NoteVisibility enum type + db.session.execute(text(""" + DO $$ BEGIN + CREATE TYPE notevisibility AS ENUM ('Private', 'Team', 'Company'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + """)) + + db.session.execute(text(""" + CREATE TABLE note ( + id SERIAL PRIMARY KEY, + title VARCHAR(200) NOT NULL, + content TEXT NOT NULL, + slug VARCHAR(100) NOT NULL, + visibility notevisibility NOT NULL DEFAULT 'Private', + company_id INTEGER NOT NULL, + created_by_id INTEGER NOT NULL, + project_id INTEGER, + task_id INTEGER, + tags TEXT[], + is_archived BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (company_id) REFERENCES company (id), + FOREIGN KEY (created_by_id) REFERENCES "user" (id), + FOREIGN KEY (project_id) REFERENCES project (id), + FOREIGN KEY (task_id) REFERENCES task (id) + ) + """)) + + # Create note_link table + db.session.execute(text(""" + CREATE TABLE note_link ( + source_note_id INTEGER NOT NULL, + target_note_id INTEGER NOT NULL, + link_type VARCHAR(50) DEFAULT 'related', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (source_note_id, target_note_id), + FOREIGN KEY (source_note_id) REFERENCES note (id) ON DELETE CASCADE, + FOREIGN KEY (target_note_id) REFERENCES note (id) ON DELETE CASCADE + ) + """)) + + # Create indexes + db.session.execute(text("CREATE INDEX idx_note_company ON note(company_id)")) + db.session.execute(text("CREATE INDEX idx_note_created_by ON note(created_by_id)")) + db.session.execute(text("CREATE INDEX idx_note_project ON note(project_id)")) + db.session.execute(text("CREATE INDEX idx_note_task ON note(task_id)")) + db.session.execute(text("CREATE INDEX idx_note_slug ON note(company_id, slug)")) + db.session.execute(text("CREATE INDEX idx_note_visibility ON note(visibility)")) + db.session.execute(text("CREATE INDEX idx_note_archived ON note(archived)")) + db.session.execute(text("CREATE INDEX idx_note_created_at ON note(created_at DESC)")) + db.session.execute(text("CREATE INDEX idx_note_link_source ON note_link(source_note_id)")) + db.session.execute(text("CREATE INDEX idx_note_link_target ON note_link(target_note_id)")) + + db.session.commit() + print("PostgreSQL schema migration completed successfully!") except Exception as e: @@ -1482,6 +1551,94 @@ def migrate_comment_system(db_file=None): conn.close() +def migrate_notes_system(db_file=None): + """Migrate to add Notes system with markdown support.""" + db_path = get_db_path(db_file) + + print(f"Migrating Notes system in {db_path}...") + + if not os.path.exists(db_path): + print(f"Database file {db_path} does not exist. Run basic migration first.") + return False + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + try: + # Check if note table already exists + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='note'") + if cursor.fetchone(): + print("Note table already exists. Skipping migration.") + return True + + print("Creating Notes system tables...") + + # Create note table + cursor.execute(""" + CREATE TABLE note ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title VARCHAR(200) NOT NULL, + content TEXT NOT NULL, + slug VARCHAR(100) NOT NULL, + visibility VARCHAR(20) NOT NULL DEFAULT 'Private', + company_id INTEGER NOT NULL, + created_by_id INTEGER NOT NULL, + project_id INTEGER, + task_id INTEGER, + tags TEXT, + archived BOOLEAN DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (company_id) REFERENCES company (id), + FOREIGN KEY (created_by_id) REFERENCES user (id), + FOREIGN KEY (project_id) REFERENCES project (id), + FOREIGN KEY (task_id) REFERENCES task (id) + ) + """) + + # Create note_link table for linking notes + cursor.execute(""" + CREATE TABLE note_link ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + source_note_id INTEGER NOT NULL, + target_note_id INTEGER NOT NULL, + link_type VARCHAR(50) DEFAULT 'related', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by_id INTEGER NOT NULL, + FOREIGN KEY (source_note_id) REFERENCES note (id) ON DELETE CASCADE, + FOREIGN KEY (target_note_id) REFERENCES note (id) ON DELETE CASCADE, + FOREIGN KEY (created_by_id) REFERENCES user (id), + UNIQUE(source_note_id, target_note_id) + ) + """) + + # Create indexes for better performance + cursor.execute("CREATE INDEX idx_note_company ON note(company_id)") + cursor.execute("CREATE INDEX idx_note_created_by ON note(created_by_id)") + cursor.execute("CREATE INDEX idx_note_project ON note(project_id)") + cursor.execute("CREATE INDEX idx_note_task ON note(task_id)") + cursor.execute("CREATE INDEX idx_note_slug ON note(company_id, slug)") + cursor.execute("CREATE INDEX idx_note_visibility ON note(visibility)") + cursor.execute("CREATE INDEX idx_note_archived ON note(archived)") + cursor.execute("CREATE INDEX idx_note_created_at ON note(created_at DESC)") + + # Create indexes for note links + cursor.execute("CREATE INDEX idx_note_link_source ON note_link(source_note_id)") + cursor.execute("CREATE INDEX idx_note_link_target ON note_link(target_note_id)") + + conn.commit() + print("Notes system migration completed successfully!") + return True + + except Exception as e: + print(f"Error during Notes system migration: {e}") + conn.rollback() + return False + + finally: + conn.close() + + def main(): """Main function with command line interface.""" parser = argparse.ArgumentParser(description='TimeTrack Database Migration Tool') diff --git a/models.py b/models.py index 68de2e6..1c87c0c 100644 --- a/models.py +++ b/models.py @@ -1241,4 +1241,180 @@ def can_user_access(self, user): user_level = role_hierarchy.get(user.role, 0) required_level = role_hierarchy.get(self.required_role, 0) - return user_level >= required_level \ No newline at end of file + return user_level >= required_level + + +# Note Sharing Visibility +class NoteVisibility(enum.Enum): + PRIVATE = "Private" + TEAM = "Team" + COMPANY = "Company" + + +class Note(db.Model): + """Markdown notes with sharing capabilities""" + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(200), nullable=False) + content = db.Column(db.Text, nullable=False) # Markdown content + slug = db.Column(db.String(100), nullable=False) # URL-friendly identifier + + # Visibility and sharing + visibility = db.Column(db.Enum(NoteVisibility), nullable=False, default=NoteVisibility.PRIVATE) + + # Metadata + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) + + # Associations + created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + company_id = db.Column(db.Integer, db.ForeignKey('company.id'), nullable=False) + + # Optional associations + team_id = db.Column(db.Integer, db.ForeignKey('team.id'), nullable=True) # For team-specific notes + project_id = db.Column(db.Integer, db.ForeignKey('project.id'), nullable=True) # Link to project + task_id = db.Column(db.Integer, db.ForeignKey('task.id'), nullable=True) # Link to task + + # Tags for organization + tags = db.Column(db.String(500)) # Comma-separated tags + + # Pin important notes + is_pinned = db.Column(db.Boolean, default=False) + + # Soft delete + is_archived = db.Column(db.Boolean, default=False) + archived_at = db.Column(db.DateTime, nullable=True) + + # Relationships + created_by = db.relationship('User', foreign_keys=[created_by_id], backref='notes') + company = db.relationship('Company', backref='notes') + team = db.relationship('Team', backref='notes') + project = db.relationship('Project', backref='notes') + task = db.relationship('Task', backref='notes') + + # Unique constraint on slug per company + __table_args__ = (db.UniqueConstraint('company_id', 'slug', name='uq_note_slug_per_company'),) + + def __repr__(self): + return f'' + + def generate_slug(self): + """Generate URL-friendly slug from title""" + import re + # Remove special characters and convert to lowercase + slug = re.sub(r'[^\w\s-]', '', self.title.lower()) + # Replace spaces with hyphens + slug = re.sub(r'[-\s]+', '-', slug) + # Remove leading/trailing hyphens + slug = slug.strip('-') + + # Ensure uniqueness within company + base_slug = slug + counter = 1 + while Note.query.filter_by(company_id=self.company_id, slug=slug).filter(Note.id != self.id).first(): + slug = f"{base_slug}-{counter}" + counter += 1 + + return slug + + def can_user_view(self, user): + """Check if user can view this note""" + # Creator can always view + if user.id == self.created_by_id: + return True + + # Check company match + if user.company_id != self.company_id: + return False + + # Check visibility + if self.visibility == NoteVisibility.COMPANY: + return True + elif self.visibility == NoteVisibility.TEAM: + # Check if user is in the same team + if self.team_id and user.team_id == self.team_id: + return True + # Admins can view all team notes + if user.role in [Role.ADMIN, Role.SYSTEM_ADMIN]: + return True + + return False + + def can_user_edit(self, user): + """Check if user can edit this note""" + # Creator can always edit + if user.id == self.created_by_id: + return True + + # Admins can edit company notes + if user.role in [Role.ADMIN, Role.SYSTEM_ADMIN] and user.company_id == self.company_id: + return True + + return False + + def get_tags_list(self): + """Get tags as a list""" + if not self.tags: + return [] + return [tag.strip() for tag in self.tags.split(',') if tag.strip()] + + def set_tags_list(self, tags_list): + """Set tags from a list""" + self.tags = ','.join(tags_list) if tags_list else None + + def get_preview(self, length=200): + """Get a plain text preview of the note content""" + # Strip markdown formatting for preview + import re + text = self.content + # Remove headers + text = re.sub(r'^#+\s+', '', text, flags=re.MULTILINE) + # Remove emphasis + text = re.sub(r'\*{1,2}([^\*]+)\*{1,2}', r'\1', text) + text = re.sub(r'_{1,2}([^_]+)_{1,2}', r'\1', text) + # Remove links + text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text) + # Remove code blocks + text = re.sub(r'```[^`]*```', '', text, flags=re.DOTALL) + text = re.sub(r'`([^`]+)`', r'\1', text) + # Clean up whitespace + text = ' '.join(text.split()) + + if len(text) > length: + return text[:length] + '...' + return text + + def render_html(self): + """Render markdown content to HTML""" + try: + import markdown + # Use extensions for better markdown support + html = markdown.markdown(self.content, extensions=['extra', 'codehilite', 'toc']) + return html + except ImportError: + # Fallback if markdown not installed + return f'
{self.content}
' + + +class NoteLink(db.Model): + """Links between notes for creating relationships""" + id = db.Column(db.Integer, primary_key=True) + + # Source and target notes + source_note_id = db.Column(db.Integer, db.ForeignKey('note.id'), nullable=False) + target_note_id = db.Column(db.Integer, db.ForeignKey('note.id'), nullable=False) + + # Link metadata + link_type = db.Column(db.String(50), default='related') # related, parent, child, etc. + created_at = db.Column(db.DateTime, default=datetime.now) + created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + + # Relationships + source_note = db.relationship('Note', foreign_keys=[source_note_id], backref='outgoing_links') + target_note = db.relationship('Note', foreign_keys=[target_note_id], backref='incoming_links') + created_by = db.relationship('User', foreign_keys=[created_by_id]) + + # Unique constraint to prevent duplicate links + __table_args__ = (db.UniqueConstraint('source_note_id', 'target_note_id', name='uq_note_link'),) + + def __repr__(self): + return f' {self.target_note_id}>' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 05b1a27..7df6e54 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ pandas==1.5.3 xlsxwriter==3.1.2 Flask-Mail==0.9.1 psycopg2-binary==2.9.9 +markdown==3.4.4 diff --git a/static/js/ace/ace.js b/static/js/ace/ace.js new file mode 100644 index 0000000..cc90862 --- /dev/null +++ b/static/js/ace/ace.js @@ -0,0 +1,23 @@ +(function(){function o(n){var i=e;n&&(e[n]||(e[n]={}),i=e[n]);if(!i.define||!i.define.packaged)t.original=i.define,i.define=t,i.define.packaged=!0;if(!i.require||!i.require.packaged)r.original=i.require,i.require=r,i.require.packaged=!0}var ACE_NAMESPACE="",e=function(){return this}();!e&&typeof window!="undefined"&&(e=window);if(!ACE_NAMESPACE&&typeof requirejs!="undefined")return;var t=function(e,n,r){if(typeof e!="string"){t.original?t.original.apply(this,arguments):(console.error("dropping module because define wasn't a string."),console.trace());return}arguments.length==2&&(r=n),t.modules[e]||(t.payloads[e]=r,t.modules[e]=null)};t.modules={},t.payloads={};var n=function(e,t,n){if(typeof t=="string"){var i=s(e,t);if(i!=undefined)return n&&n(),i}else if(Object.prototype.toString.call(t)==="[object Array]"){var o=[];for(var u=0,a=t.length;un.length)t=n.length;t-=e.length;var r=n.indexOf(e,t);return r!==-1&&r===t}),String.prototype.repeat||r(String.prototype,"repeat",function(e){var t="",n=this;while(e>0){e&1&&(t+=n);if(e>>=1)n+=n}return t}),String.prototype.includes||r(String.prototype,"includes",function(e,t){return this.indexOf(e,t)!=-1}),Object.assign||(Object.assign=function(e){if(e===undefined||e===null)throw new TypeError("Cannot convert undefined or null to object");var t=Object(e);for(var n=1;n>>0,r=arguments[1],i=r>>0,s=i<0?Math.max(n+i,0):Math.min(i,n),o=arguments[2],u=o===undefined?n:o>>0,a=u<0?Math.max(n+u,0):Math.min(u,n);while(s0){t&1&&(n+=e);if(t>>=1)e+=e}return n};var r=/^\s\s*/,i=/\s\s*$/;t.stringTrimLeft=function(e){return e.replace(r,"")},t.stringTrimRight=function(e){return e.replace(i,"")},t.copyObject=function(e){var t={};for(var n in e)t[n]=e[n];return t},t.copyArray=function(e){var t=[];for(var n=0,r=e.length;n65535?2:1}}),define("ace/lib/useragent",["require","exports","module"],function(e,t,n){"use strict";t.OS={LINUX:"LINUX",MAC:"MAC",WINDOWS:"WINDOWS"},t.getOS=function(){return t.isMac?t.OS.MAC:t.isLinux?t.OS.LINUX:t.OS.WINDOWS};var r=typeof navigator=="object"?navigator:{},i=(/mac|win|linux/i.exec(r.platform)||["other"])[0].toLowerCase(),s=r.userAgent||"",o=r.appName||"";t.isWin=i=="win",t.isMac=i=="mac",t.isLinux=i=="linux",t.isIE=o=="Microsoft Internet Explorer"||o.indexOf("MSAppHost")>=0?parseFloat((s.match(/(?:MSIE |Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/)||[])[1]):parseFloat((s.match(/(?:Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/)||[])[1]),t.isOldIE=t.isIE&&t.isIE<9,t.isGecko=t.isMozilla=s.match(/ Gecko\/\d+/),t.isOpera=typeof opera=="object"&&Object.prototype.toString.call(window["opera"])=="[object Opera]",t.isWebKit=parseFloat(s.split("WebKit/")[1])||undefined,t.isChrome=parseFloat(s.split(" Chrome/")[1])||undefined,t.isSafari=parseFloat(s.split(" Safari/")[1])&&!t.isChrome||undefined,t.isEdge=parseFloat(s.split(" Edge/")[1])||undefined,t.isAIR=s.indexOf("AdobeAIR")>=0,t.isAndroid=s.indexOf("Android")>=0,t.isChromeOS=s.indexOf(" CrOS ")>=0,t.isIOS=/iPad|iPhone|iPod/.test(s)&&!window.MSStream,t.isIOS&&(t.isMac=!0),t.isMobile=t.isIOS||t.isAndroid}),define("ace/lib/dom",["require","exports","module","ace/lib/useragent"],function(e,t,n){"use strict";function u(){var e=o;o=null,e&&e.forEach(function(e){a(e[0],e[1])})}function a(e,n,r){if(typeof document=="undefined")return;if(o)if(r)u();else if(r===!1)return o.push([e,n]);if(s)return;var i=r;if(!r||!r.getRootNode)i=document;else{i=r.getRootNode();if(!i||i==r)i=document}var a=i.ownerDocument||i;if(n&&t.hasCssString(n,i))return null;n&&(e+="\n/*# sourceURL=ace/css/"+n+" */");var f=t.createElement("style");f.appendChild(a.createTextNode(e)),n&&(f.id=n),i==a&&(i=t.getDocumentHead(a)),i.insertBefore(f,i.firstChild)}var r=e("./useragent"),i="http://www.w3.org/1999/xhtml";t.buildDom=function l(e,t,n){if(typeof e=="string"&&e){var r=document.createTextNode(e);return t&&t.appendChild(r),r}if(!Array.isArray(e))return e&&e.appendChild&&t&&t.appendChild(e),e;if(typeof e[0]!="string"||!e[0]){var i=[];for(var s=0;s=1.5:!0,r.isChromeOS&&(t.HI_DPI=!1);if(typeof document!="undefined"){var f=document.createElement("div");t.HI_DPI&&f.style.transform!==undefined&&(t.HAS_CSS_TRANSFORMS=!0),!r.isEdge&&typeof f.style.animationName!="undefined"&&(t.HAS_CSS_ANIMATION=!0),f=null}t.HAS_CSS_TRANSFORMS?t.translate=function(e,t,n){e.style.transform="translate("+Math.round(t)+"px, "+Math.round(n)+"px)"}:t.translate=function(e,t,n){e.style.top=Math.round(n)+"px",e.style.left=Math.round(t)+"px"}}),define("ace/lib/net",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";var r=e("./dom");t.get=function(e,t){var n=new XMLHttpRequest;n.open("GET",e,!0),n.onreadystatechange=function(){n.readyState===4&&t(n.responseText)},n.send(null)},t.loadScript=function(e,t){var n=r.getDocumentHead(),i=document.createElement("script");i.src=e,n.appendChild(i),i.onload=i.onreadystatechange=function(e,n){if(n||!i.readyState||i.readyState=="loaded"||i.readyState=="complete")i=i.onload=i.onreadystatechange=null,n||t()}},t.qualifyURL=function(e){var t=document.createElement("a");return t.href=e,t.href}}),define("ace/lib/oop",["require","exports","module"],function(e,t,n){"use strict";t.inherits=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})},t.mixin=function(e,t){for(var n in t)e[n]=t[n];return e},t.implement=function(e,n){t.mixin(e,n)}}),define("ace/lib/event_emitter",["require","exports","module"],function(e,t,n){"use strict";var r={},i=function(){this.propagationStopped=!0},s=function(){this.defaultPrevented=!0};r._emit=r._dispatchEvent=function(e,t){this._eventRegistry||(this._eventRegistry={}),this._defaultHandlers||(this._defaultHandlers={});var n=this._eventRegistry[e]||[],r=this._defaultHandlers[e];if(!n.length&&!r)return;if(typeof t!="object"||!t)t={};t.type||(t.type=e),t.stopPropagation||(t.stopPropagation=i),t.preventDefault||(t.preventDefault=s),n=n.slice();for(var o=0;o1&&(i=n[n.length-2]);var o=u[t+"Path"];return o==null?o=u.basePath:r=="/"&&(t=r=""),o&&o.slice(-1)!="/"&&(o+="/"),o+t+r+i+this.get("suffix")},t.setModuleUrl=function(e,t){return u.$moduleUrls[e]=t};var a=function(t,n){if(t==="ace/theme/textmate"||t==="./theme/textmate")return n(null,e("./theme/textmate"));if(f)return f(t,n);console.error("loader is not configured")},f;t.setLoader=function(e){f=e},t.dynamicModules=Object.create(null),t.$loading={},t.$loaded={},t.loadModule=function(e,n){var r;if(Array.isArray(e))var s=e[0],o=e[1];else if(typeof e=="string")var o=e;var u=function(e){if(e&&!t.$loading[o])return n&&n(e);t.$loading[o]||(t.$loading[o]=[]),t.$loading[o].push(n);if(t.$loading[o].length>1)return;var r=function(){a(o,function(e,n){n&&(t.$loaded[o]=n),t._emit("load.module",{name:o,module:n});var r=t.$loading[o];t.$loading[o]=null,r.forEach(function(e){e&&e(n)})})};if(!t.get("packaged"))return r();i.loadScript(t.moduleUrl(o,s),r),l()};if(t.dynamicModules[o])t.dynamicModules[o]().then(function(e){e.default?u(e.default):u(e)});else{try{r=this.$require(o)}catch(f){}u(r||t.$loaded[o])}},t.$require=function(e){if(typeof n["require"]=="function"){var t="require";return n[t](e)}},t.setModuleLoader=function(e,n){t.dynamicModules[e]=n};var l=function(){!u.basePath&&!u.workerPath&&!u.modePath&&!u.themePath&&!Object.keys(u.$moduleUrls).length&&(console.error("Unable to infer path to ace from script src,","use ace.config.set('basePath', 'path') to enable dynamic loading of modes and themes","or with webpack use ace/webpack-resolver"),l=function(){})};t.version="1.32.2"}),define("ace/loader_build",["require","exports","module","ace/lib/fixoldbrowsers","ace/config"],function(e,t,n){"use strict";function s(t){if(!i||!i.document)return;r.set("packaged",t||e.packaged||n.packaged||i.define&&define.packaged);var s={},u="",a=document.currentScript||document._currentScript,f=a&&a.ownerDocument||document;a&&a.src&&(u=a.src.split(/[?#]/)[0].split("/").slice(0,-1).join("/")||"");var l=f.getElementsByTagName("script");for(var c=0;c ["+this.end.row+"/"+this.end.column+"]"},e.prototype.contains=function(e,t){return this.compare(e,t)==0},e.prototype.compareRange=function(e){var t,n=e.end,r=e.start;return t=this.compare(n.row,n.column),t==1?(t=this.compare(r.row,r.column),t==1?2:t==0?1:0):t==-1?-2:(t=this.compare(r.row,r.column),t==-1?-1:t==1?42:0)},e.prototype.comparePoint=function(e){return this.compare(e.row,e.column)},e.prototype.containsRange=function(e){return this.comparePoint(e.start)==0&&this.comparePoint(e.end)==0},e.prototype.intersects=function(e){var t=this.compareRange(e);return t==-1||t==0||t==1},e.prototype.isEnd=function(e,t){return this.end.row==e&&this.end.column==t},e.prototype.isStart=function(e,t){return this.start.row==e&&this.start.column==t},e.prototype.setStart=function(e,t){typeof e=="object"?(this.start.column=e.column,this.start.row=e.row):(this.start.row=e,this.start.column=t)},e.prototype.setEnd=function(e,t){typeof e=="object"?(this.end.column=e.column,this.end.row=e.row):(this.end.row=e,this.end.column=t)},e.prototype.inside=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)||this.isStart(e,t)?!1:!0:!1},e.prototype.insideStart=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)?!1:!0:!1},e.prototype.insideEnd=function(e,t){return this.compare(e,t)==0?this.isStart(e,t)?!1:!0:!1},e.prototype.compare=function(e,t){return!this.isMultiLine()&&e===this.start.row?tthis.end.column?1:0:ethis.end.row?1:this.start.row===e?t>=this.start.column?0:-1:this.end.row===e?t<=this.end.column?0:1:0},e.prototype.compareStart=function(e,t){return this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},e.prototype.compareEnd=function(e,t){return this.end.row==e&&this.end.column==t?1:this.compare(e,t)},e.prototype.compareInside=function(e,t){return this.end.row==e&&this.end.column==t?1:this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},e.prototype.clipRows=function(t,n){if(this.end.row>n)var r={row:n+1,column:0};else if(this.end.rown)var i={row:n+1,column:0};else if(this.start.row1?(u++,u>4&&(u=1)):u=1;if(i.isIE){var o=Math.abs(e.clientX-a)>5||Math.abs(e.clientY-f)>5;if(!l||o)u=1;l&&clearTimeout(l),l=setTimeout(function(){l=null},n[u-1]||600),u==1&&(a=e.clientX,f=e.clientY)}e._clicks=u,r[s]("mousedown",e);if(u>4)u=0;else if(u>1)return r[s](h[u],e)}var u=0,a,f,l,h={2:"dblclick",3:"tripleclick",4:"quadclick"};Array.isArray(e)||(e=[e]),e.forEach(function(e){c(e,"mousedown",p,o)})};var p=function(e){return 0|(e.ctrlKey?1:0)|(e.altKey?2:0)|(e.shiftKey?4:0)|(e.metaKey?8:0)};t.getModifierString=function(e){return r.KEY_MODS[p(e)]},t.addCommandKeyListener=function(e,n,r){var i=null;c(e,"keydown",function(e){s[e.keyCode]=(s[e.keyCode]||0)+1;var t=d(n,e,e.keyCode);return i=e.defaultPrevented,t},r),c(e,"keypress",function(e){i&&(e.ctrlKey||e.altKey||e.shiftKey||e.metaKey)&&(t.stopEvent(e),i=null)},r),c(e,"keyup",function(e){s[e.keyCode]=null},r),s||(v(),c(window,"focus",v))};if(typeof window=="object"&&window.postMessage&&!i.isOldIE){var m=1;t.nextTick=function(e,n){n=n||window;var r="zero-timeout-message-"+m++,i=function(s){s.data==r&&(t.stopPropagation(s),h(n,"message",i),e())};c(n,"message",i),n.postMessage(r,"*")}}t.$idleBlocked=!1,t.onIdle=function(e,n){return setTimeout(function r(){t.$idleBlocked?setTimeout(r,100):e()},n)},t.$idleBlockId=null,t.blockIdle=function(e){t.$idleBlockId&&clearTimeout(t.$idleBlockId),t.$idleBlocked=!0,t.$idleBlockId=setTimeout(function(){t.$idleBlocked=!1},e||100)},t.nextFrame=typeof window=="object"&&(window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||window.oRequestAnimationFrame),t.nextFrame?t.nextFrame=t.nextFrame.bind(window):t.nextFrame=function(e){setTimeout(e,17)}}),define("ace/clipboard",["require","exports","module"],function(e,t,n){"use strict";var r;n.exports={lineMode:!1,pasteCancelled:function(){return r&&r>Date.now()-50?!0:r=!1},cancel:function(){r=Date.now()}}}),define("ace/keyboard/textinput",["require","exports","module","ace/lib/event","ace/config","ace/lib/useragent","ace/lib/dom","ace/lib/lang","ace/clipboard","ace/lib/keys"],function(e,t,n){"use strict";var r=e("../lib/event"),i=e("../config").nls,s=e("../lib/useragent"),o=e("../lib/dom"),u=e("../lib/lang"),a=e("../clipboard"),f=s.isChrome<18,l=s.isIE,c=s.isChrome>63,h=400,p=e("../lib/keys"),d=p.KEY_MODS,v=s.isIOS,m=v?/\s/:/\n/,g=s.isMobile,y;y=function(e,t){function Q(){T=!0,n.blur(),n.focus(),T=!1}function Y(e){e.keyCode==27&&n.value.lengthk&&N[s]=="\n")o=p.end;else if(rk&&N.slice(0,s).split("\n").length>2)o=p.down;else if(s>k&&N[s-1]==" ")o=p.right,u=d.option;else if(s>k||s==k&&k!=C&&r==s)o=p.right;r!==s&&(u|=d.shift);if(o){var a=t.onCommandKey({},u,o);if(!a&&t.commands){o=p.keyCodeToString(o);var f=t.commands.findKeyCommand(u,o);f&&t.execCommand(f)}C=r,k=s,H("")}};document.addEventListener("selectionchange",s),t.on("destroy",function(){document.removeEventListener("selectionchange",s)})}var n=o.createElement("textarea");n.className="ace_text-input",n.setAttribute("wrap","off"),n.setAttribute("autocorrect","off"),n.setAttribute("autocapitalize","off"),n.setAttribute("spellcheck","false"),n.style.opacity="0",e.insertBefore(n,e.firstChild);var y=!1,b=!1,w=!1,E=!1,S="";g||(n.style.fontSize="1px");var x=!1,T=!1,N="",C=0,k=0,L=0,A=Number.MAX_SAFE_INTEGER,O=Number.MIN_SAFE_INTEGER,M=0;try{var _=document.activeElement===n}catch(D){}this.setNumberOfExtraLines=function(e){A=Number.MAX_SAFE_INTEGER,O=Number.MIN_SAFE_INTEGER;if(e<0){M=0;return}M=e},this.setAriaOptions=function(e){e.activeDescendant?(n.setAttribute("aria-haspopup","true"),n.setAttribute("aria-autocomplete",e.inline?"both":"list"),n.setAttribute("aria-activedescendant",e.activeDescendant)):(n.setAttribute("aria-haspopup","false"),n.setAttribute("aria-autocomplete","both"),n.removeAttribute("aria-activedescendant")),e.role&&n.setAttribute("role",e.role);if(e.setLabel){n.setAttribute("aria-roledescription",i("editor"));if(t.session){var r=t.session.selection.cursor.row;n.setAttribute("aria-label",i("Cursor at row $0",[r+1]))}}},this.setAriaOptions({role:"textbox"}),r.addListener(n,"blur",function(e){if(T)return;t.onBlur(e),_=!1},t),r.addListener(n,"focus",function(e){if(T)return;_=!0;if(s.isEdge)try{if(!document.hasFocus())return}catch(e){}t.onFocus(e),s.isEdge?setTimeout(H):H()},t),this.$focusScroll=!1,this.focus=function(){this.setAriaOptions({setLabel:t.renderer.enableKeyboardAccessibility});if(S||c||this.$focusScroll=="browser")return n.focus({preventScroll:!0});var e=n.style.top;n.style.position="fixed",n.style.top="0px";try{var r=n.getBoundingClientRect().top!=0}catch(i){return}var s=[];if(r){var o=n.parentElement;while(o&&o.nodeType==1)s.push(o),o.setAttribute("ace_nocontext","true"),!o.parentElement&&o.getRootNode?o=o.getRootNode().host:o=o.parentElement}n.focus({preventScroll:!0}),r&&s.forEach(function(e){e.removeAttribute("ace_nocontext")}),setTimeout(function(){n.style.position="",n.style.top=="0px"&&(n.style.top=e)},0)},this.blur=function(){n.blur()},this.isFocused=function(){return _},t.on("beforeEndOperation",function(){var e=t.curOp,r=e&&e.command&&e.command.name;if(r=="insertstring")return;var i=r&&(e.docChanged||e.selectionChanged);w&&i&&(N=n.value="",K()),H()});var P=function(e,n){var r=n;for(var i=1;i<=e-A&&i<2*M+1;i++)r+=t.session.getLine(e-i).length+1;return r},H=v?function(e){if(!_||y&&!e||E)return;e||(e="");var r="\n ab"+e+"cde fg\n";r!=n.value&&(n.value=N=r);var i=4,s=4+(e.length||(t.selection.isEmpty()?0:1));(C!=i||k!=s)&&n.setSelectionRange(i,s),C=i,k=s}:function(){if(w||E)return;if(!_&&!I)return;w=!0;var e=0,r=0,i="";if(t.session){var s=t.selection,o=s.getRange(),u=s.cursor.row;if(u===O+1)A=O+1,O=A+2*M;else if(u===A-1)O=A-1,A=O-2*M;else if(uO+1)A=u>M?u-M:0,O=u>M?u+M:2*M;var a=[];for(var f=A;f<=O;f++)a.push(t.session.getLine(f));i=a.join("\n"),e=P(o.start.row,o.start.column),r=P(o.end.row,o.end.column);if(o.start.rowO){var c=t.session.getLine(O+1);r=o.end.row>O+1?c.length:o.end.column,r+=i.length+1,i=i+"\n"+c}else g&&u>0&&(i="\n"+i,r+=1,e+=1);i.length>h&&(e=N.length&&e.value===N&&N&&e.selectionEnd!==k},j=function(e){if(w)return;y?y=!1:B(n)?(t.selectAll(),H()):g&&n.selectionStart!=C&&H()},F=null;this.setInputHandler=function(e){F=e},this.getInputHandler=function(){return F};var I=!1,q=function(e,r){I&&(I=!1);if(b)return H(),e&&t.onPaste(e),b=!1,"";var i=n.selectionStart,o=n.selectionEnd,u=C,a=N.length-k,f=e,l=e.length-i,c=e.length-o,h=0;while(u>0&&N[h]==e[h])h++,u--;f=f.slice(h),h=1;while(a>0&&N.length-h>C-1&&N[N.length-h]==e[e.length-h])h++,a--;l-=h-1,c-=h-1;var p=f.length-h+1;p<0&&(u=-p,p=0),f=f.slice(0,p);if(!r&&!f&&!l&&!u&&!a&&!c)return"";E=!0;var d=!1;return s.isAndroid&&f==". "&&(f=" ",d=!0),f&&!u&&!a&&!l&&!c||x?t.onTextInput(f):t.onTextInput(f,{extendLeft:u,extendRight:a,restoreStart:l,restoreEnd:c}),E=!1,N=e,C=i,k=o,L=c,d?"\n":f},R=function(e){if(w)return J();if(e&&e.inputType){if(e.inputType=="historyUndo")return t.execCommand("undo");if(e.inputType=="historyRedo")return t.execCommand("redo")}var r=n.value,i=q(r,!0);(r.length>h+100||m.test(i)||g&&C<1&&C==k)&&H()},U=function(e,t,n){var r=e.clipboardData||window.clipboardData;if(!r||f)return;var i=l||n?"Text":"text/plain";try{return t?r.setData(i,t)!==!1:r.getData(i)}catch(e){if(!n)return U(e,t,!0)}},z=function(e,i){var s=t.getCopyText();if(!s)return r.preventDefault(e);U(e,s)?(v&&(H(s),y=s,setTimeout(function(){y=!1},10)),i?t.onCut():t.onCopy(),r.preventDefault(e)):(y=!0,n.value=s,n.select(),setTimeout(function(){y=!1,H(),i?t.onCut():t.onCopy()}))},W=function(e){z(e,!0)},X=function(e){z(e,!1)},V=function(e){var i=U(e);if(a.pasteCancelled())return;typeof i=="string"?(i&&t.onPaste(i,e),s.isIE&&setTimeout(H),r.preventDefault(e)):(n.value="",b=!0)};r.addCommandKeyListener(n,t.onCommandKey.bind(t),t),r.addListener(n,"select",j,t),r.addListener(n,"input",R,t),r.addListener(n,"cut",W,t),r.addListener(n,"copy",X,t),r.addListener(n,"paste",V,t),(!("oncut"in n)||!("oncopy"in n)||!("onpaste"in n))&&r.addListener(e,"keydown",function(e){if(s.isMac&&!e.metaKey||!e.ctrlKey)return;switch(e.keyCode){case 67:X(e);break;case 86:V(e);break;case 88:W(e)}},t);var $=function(e){if(w||!t.onCompositionStart||t.$readOnly)return;w={};if(x)return;e.data&&(w.useTextareaForIME=!1),setTimeout(J,0),t._signal("compositionStart"),t.on("mousedown",Q);var r=t.getSelectionRange();r.end.row=r.start.row,r.end.column=r.start.column,w.markerRange=r,w.selectionStart=C,t.onCompositionStart(w),w.useTextareaForIME?(N=n.value="",C=0,k=0):(n.msGetInputContext&&(w.context=n.msGetInputContext()),n.getInputContext&&(w.context=n.getInputContext()))},J=function(){if(!w||!t.onCompositionUpdate||t.$readOnly)return;if(x)return Q();if(w.useTextareaForIME)t.onCompositionUpdate(n.value);else{var e=n.value;q(e),w.markerRange&&(w.context&&(w.markerRange.start.column=w.selectionStart=w.context.compositionStartOffset),w.markerRange.end.column=w.markerRange.start.column+k-w.selectionStart+L)}},K=function(e){if(!t.onCompositionEnd||t.$readOnly)return;w=!1,t.onCompositionEnd(),t.off("mousedown",Q),e&&R()},G=u.delayedCall(J,50).schedule.bind(null,null);r.addListener(n,"compositionstart",$,t),r.addListener(n,"compositionupdate",J,t),r.addListener(n,"keyup",Y,t),r.addListener(n,"keydown",G,t),r.addListener(n,"compositionend",K,t),this.getElement=function(){return n},this.setCommandMode=function(e){x=e,n.readOnly=!1},this.setReadOnly=function(e){x||(n.readOnly=e)},this.setCopyWithEmptySelection=function(e){},this.onContextMenu=function(e){I=!0,H(),t._emit("nativecontextmenu",{target:t,domEvent:e}),this.moveToMouse(e,!0)},this.moveToMouse=function(e,i){S||(S=n.style.cssText),n.style.cssText=(i?"z-index:100000;":"")+(s.isIE?"opacity:0.1;":"")+"text-indent: -"+(C+k)*t.renderer.characterWidth*.5+"px;";var u=t.container.getBoundingClientRect(),a=o.computedStyle(t.container),f=u.top+(parseInt(a.borderTopWidth)||0),l=u.left+(parseInt(u.borderLeftWidth)||0),c=u.bottom-f-n.clientHeight-2,h=function(e){o.translate(n,e.clientX-l-2,Math.min(e.clientY-f-2,c))};h(e);if(e.type!="mousedown")return;t.renderer.$isMousePressed=!0,clearTimeout(Z),s.isWin&&r.capture(t.container,h,et)},this.onContextMenuClose=et;var Z,tt=function(e){t.textInput.onContextMenu(e),et()};r.addListener(n,"mouseup",tt,t),r.addListener(n,"mousedown",function(e){e.preventDefault(),et()},t),r.addListener(t.renderer.scroller,"contextmenu",tt,t),r.addListener(n,"contextmenu",tt,t),v&&nt(e,t,n),this.destroy=function(){n.parentElement&&n.parentElement.removeChild(n)}},t.TextInput=y,t.$setUserAgentForTests=function(e,t){g=e,v=t}}),define("ace/mouse/default_handlers",["require","exports","module","ace/lib/useragent"],function(e,t,n){"use strict";function u(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}function a(e,t){if(e.start.row==e.end.row)var n=2*t.column-e.start.column-e.end.column;else if(e.start.row==e.end.row-1&&!e.start.column&&!e.end.column)var n=t.column-4;else var n=2*t.row-e.start.row-e.end.row;return n<0?{cursor:e.start,anchor:e.end}:{cursor:e.end,anchor:e.start}}var r=e("../lib/useragent"),i=0,s=550,o=function(){function e(e){e.$clickSelection=null;var t=e.editor;t.setDefaultHandler("mousedown",this.onMouseDown.bind(e)),t.setDefaultHandler("dblclick",this.onDoubleClick.bind(e)),t.setDefaultHandler("tripleclick",this.onTripleClick.bind(e)),t.setDefaultHandler("quadclick",this.onQuadClick.bind(e)),t.setDefaultHandler("mousewheel",this.onMouseWheel.bind(e));var n=["select","startSelect","selectEnd","selectAllEnd","selectByWordsEnd","selectByLinesEnd","dragWait","dragWaitEnd","focusWait"];n.forEach(function(t){e[t]=this[t]},this),e.selectByLines=this.extendSelectionBy.bind(e,"getLineRange"),e.selectByWords=this.extendSelectionBy.bind(e,"getWordRange")}return e.prototype.onMouseDown=function(e){var t=e.inSelection(),n=e.getDocumentPosition();this.mousedownEvent=e;var i=this.editor,s=e.getButton();if(s!==0){var o=i.getSelectionRange(),u=o.isEmpty();(u||s==1)&&i.selection.moveToPosition(n),s==2&&(i.textInput.onContextMenu(e.domEvent),r.isMozilla||e.preventDefault());return}this.mousedownEvent.time=Date.now();if(t&&!i.isFocused()){i.focus();if(this.$focusTimeout&&!this.$clickSelection&&!i.inMultiSelectMode){this.setState("focusWait"),this.captureMouse(e);return}}return this.captureMouse(e),this.startSelect(n,e.domEvent._clicks>1),e.preventDefault()},e.prototype.startSelect=function(e,t){e=e||this.editor.renderer.screenToTextCoordinates(this.x,this.y);var n=this.editor;if(!this.mousedownEvent)return;this.mousedownEvent.getShiftKey()?n.selection.selectToPosition(e):t||n.selection.moveToPosition(e),t||this.select(),n.setStyle("ace_selecting"),this.setState("select")},e.prototype.select=function(){var e,t=this.editor,n=t.renderer.screenToTextCoordinates(this.x,this.y);if(this.$clickSelection){var r=this.$clickSelection.comparePoint(n);if(r==-1)e=this.$clickSelection.end;else if(r==1)e=this.$clickSelection.start;else{var i=a(this.$clickSelection,n);n=i.cursor,e=i.anchor}t.selection.setSelectionAnchor(e.row,e.column)}t.selection.selectToPosition(n),t.renderer.scrollCursorIntoView()},e.prototype.extendSelectionBy=function(e){var t,n=this.editor,r=n.renderer.screenToTextCoordinates(this.x,this.y),i=n.selection[e](r.row,r.column);if(this.$clickSelection){var s=this.$clickSelection.comparePoint(i.start),o=this.$clickSelection.comparePoint(i.end);if(s==-1&&o<=0){t=this.$clickSelection.end;if(i.end.row!=r.row||i.end.column!=r.column)r=i.start}else if(o==1&&s>=0){t=this.$clickSelection.start;if(i.start.row!=r.row||i.start.column!=r.column)r=i.end}else if(s==-1&&o==1)r=i.end,t=i.start;else{var u=a(this.$clickSelection,r);r=u.cursor,t=u.anchor}n.selection.setSelectionAnchor(t.row,t.column)}n.selection.selectToPosition(r),n.renderer.scrollCursorIntoView()},e.prototype.selectByLinesEnd=function(){this.$clickSelection=null,this.editor.unsetStyle("ace_selecting")},e.prototype.focusWait=function(){var e=u(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y),t=Date.now();(e>i||t-this.mousedownEvent.time>this.$focusTimeout)&&this.startSelect(this.mousedownEvent.getDocumentPosition())},e.prototype.onDoubleClick=function(e){var t=e.getDocumentPosition(),n=this.editor,r=n.session,i=r.getBracketRange(t);i?(i.isEmpty()&&(i.start.column--,i.end.column++),this.setState("select")):(i=n.selection.getWordRange(t.row,t.column),this.setState("selectByWords")),this.$clickSelection=i,this.select()},e.prototype.onTripleClick=function(e){var t=e.getDocumentPosition(),n=this.editor;this.setState("selectByLines");var r=n.getSelectionRange();r.isMultiLine()&&r.contains(t.row,t.column)?(this.$clickSelection=n.selection.getLineRange(r.start.row),this.$clickSelection.end=n.selection.getLineRange(r.end.row).end):this.$clickSelection=n.selection.getLineRange(t.row),this.select()},e.prototype.onQuadClick=function(e){var t=this.editor;t.selectAll(),this.$clickSelection=t.getSelectionRange(),this.setState("selectAll")},e.prototype.onMouseWheel=function(e){if(e.getAccelKey())return;e.getShiftKey()&&e.wheelY&&!e.wheelX&&(e.wheelX=e.wheelY,e.wheelY=0);var t=this.editor;this.$lastScroll||(this.$lastScroll={t:0,vx:0,vy:0,allowed:0});var n=this.$lastScroll,r=e.domEvent.timeStamp,i=r-n.t,o=i?e.wheelX/i:n.vx,u=i?e.wheelY/i:n.vy;i=1&&t.renderer.isScrollableBy(e.wheelX*e.speed,0)&&(f=!0),a<=1&&t.renderer.isScrollableBy(0,e.wheelY*e.speed)&&(f=!0);if(f)n.allowed=r;else if(r-n.allowedn.clientHeight;r||t.preventDefault()}}),define("ace/tooltip",["require","exports","module","ace/lib/dom","ace/lib/event","ace/range","ace/lib/scroll"],function(e,t,n){"use strict";var r=this&&this.__extends||function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},e(t,n)};return function(t,n){function r(){this.constructor=t}if(typeof n!="function"&&n!==null)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");e(t,n),t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),i=this&&this.__values||function(e){var t=typeof Symbol=="function"&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&typeof e.length=="number")return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},s=e("./lib/dom"),o=e("./lib/event"),u=e("./range").Range,a=e("./lib/scroll").preventParentScroll,f="ace_tooltip",l=function(){function e(e){this.isOpen=!1,this.$element=null,this.$parentNode=e}return e.prototype.$init=function(){return this.$element=s.createElement("div"),this.$element.className=f,this.$element.style.display="none",this.$parentNode.appendChild(this.$element),this.$element},e.prototype.getElement=function(){return this.$element||this.$init()},e.prototype.setText=function(e){this.getElement().textContent=e},e.prototype.setHtml=function(e){this.getElement().innerHTML=e},e.prototype.setPosition=function(e,t){this.getElement().style.left=e+"px",this.getElement().style.top=t+"px"},e.prototype.setClassName=function(e){s.addCssClass(this.getElement(),e)},e.prototype.setTheme=function(e){this.$element.className=f+" "+(e.isDark?"ace_dark ":"")+(e.cssClass||"")},e.prototype.show=function(e,t,n){e!=null&&this.setText(e),t!=null&&n!=null&&this.setPosition(t,n),this.isOpen||(this.getElement().style.display="block",this.isOpen=!0)},e.prototype.hide=function(e){this.isOpen&&(this.getElement().style.display="none",this.getElement().className=f,this.isOpen=!1)},e.prototype.getHeight=function(){return this.getElement().offsetHeight},e.prototype.getWidth=function(){return this.getElement().offsetWidth},e.prototype.destroy=function(){this.isOpen=!1,this.$element&&this.$element.parentNode&&this.$element.parentNode.removeChild(this.$element)},e}(),c=function(){function e(){this.popups=[]}return e.prototype.addPopup=function(e){this.popups.push(e),this.updatePopups()},e.prototype.removePopup=function(e){var t=this.popups.indexOf(e);t!==-1&&(this.popups.splice(t,1),this.updatePopups())},e.prototype.updatePopups=function(){var e,t,n,r;this.popups.sort(function(e,t){return t.priority-e.priority});var s=[];try{for(var o=i(this.popups),u=o.next();!u.done;u=o.next()){var a=u.value,f=!0;try{for(var l=(n=void 0,i(s)),c=l.next();!c.done;c=l.next()){var h=c.value;if(this.doPopupsOverlap(h,a)){f=!1;break}}}catch(p){n={error:p}}finally{try{c&&!c.done&&(r=l.return)&&r.call(l)}finally{if(n)throw n.error}}f?s.push(a):a.hide()}}catch(d){e={error:d}}finally{try{u&&!u.done&&(t=o.return)&&t.call(o)}finally{if(e)throw e.error}}},e.prototype.doPopupsOverlap=function(e,t){var n=e.getElement().getBoundingClientRect(),r=t.getElement().getBoundingClientRect();return n.leftr.left&&n.topr.top},e}(),h=new c;t.popupManager=h,t.Tooltip=l;var p=function(e){function t(t){t===void 0&&(t=document.body);var n=e.call(this,t)||this;n.timeout=undefined,n.lastT=0,n.idleTime=350,n.lastEvent=undefined,n.onMouseOut=n.onMouseOut.bind(n),n.onMouseMove=n.onMouseMove.bind(n),n.waitForHover=n.waitForHover.bind(n),n.hide=n.hide.bind(n);var r=n.getElement();return r.style.whiteSpace="pre-wrap",r.style.pointerEvents="auto",r.addEventListener("mouseout",n.onMouseOut),r.tabIndex=-1,r.addEventListener("blur",function(){r.contains(document.activeElement)||this.hide()}.bind(n)),r.addEventListener("wheel",a),n}return r(t,e),t.prototype.addToEditor=function(e){e.on("mousemove",this.onMouseMove),e.on("mousedown",this.hide),e.renderer.getMouseEventTarget().addEventListener("mouseout",this.onMouseOut,!0)},t.prototype.removeFromEditor=function(e){e.off("mousemove",this.onMouseMove),e.off("mousedown",this.hide),e.renderer.getMouseEventTarget().removeEventListener("mouseout",this.onMouseOut,!0),this.timeout&&(clearTimeout(this.timeout),this.timeout=null)},t.prototype.onMouseMove=function(e,t){this.lastEvent=e,this.lastT=Date.now();var n=t.$mouseHandler.isMousePressed;if(this.isOpen){var r=this.lastEvent&&this.lastEvent.getDocumentPosition();(!this.range||!this.range.contains(r.row,r.column)||n||this.isOutsideOfText(this.lastEvent))&&this.hide()}if(this.timeout||n)return;this.lastEvent=e,this.timeout=setTimeout(this.waitForHover,this.idleTime)},t.prototype.waitForHover=function(){this.timeout&&clearTimeout(this.timeout);var e=Date.now()-this.lastT;if(this.idleTime-e>10){this.timeout=setTimeout(this.waitForHover,this.idleTime-e);return}this.timeout=null,this.lastEvent&&!this.isOutsideOfText(this.lastEvent)&&this.$gatherData(this.lastEvent,this.lastEvent.editor)},t.prototype.isOutsideOfText=function(e){var t=e.editor,n=e.getDocumentPosition(),r=t.session.getLine(n.row);if(n.column==r.length){var i=t.renderer.pixelToScreenCoordinates(e.clientX,e.clientY),s=t.session.documentToScreenPosition(n.row,n.column);if(s.column!=i.column||s.row!=i.row)return!0}return!1},t.prototype.setDataProvider=function(e){this.$gatherData=e},t.prototype.showForRange=function(e,t,n,r){var i=10;if(r&&r!=this.lastEvent)return;if(this.isOpen&&document.activeElement==this.getElement())return;var s=e.renderer;this.isOpen||(h.addPopup(this),this.$registerCloseEvents(),this.setTheme(s.theme)),this.isOpen=!0,this.addMarker(t,e.session),this.range=u.fromPoints(t.start,t.end);var o=s.textToScreenCoordinates(t.start.row,t.start.column),a=s.scroller.getBoundingClientRect();o.pageXt.session.documentToScreenRow(a.row,a.column))return f()}r.showTooltip(i);if(!r.isOpen)return;t.on("mousewheel",f);if(e.$tooltipFollowsMouse)c(u);else{var l=u.getGutterRow(),h=n.$lines.get(l);if(h){var p=h.element.querySelector(".ace_gutter_annotation"),d=p.getBoundingClientRect(),v=r.getElement().style;v.left=d.right+"px",v.top=d.bottom+"px"}else c(u)}}function f(){i&&(i=clearTimeout(i)),r.isOpen&&(r.hideTooltip(),t.off("mousewheel",f))}function c(e){r.setPosition(e.x,e.y)}var t=e.editor,n=t.renderer.$gutterLayer,r=new l(t);e.editor.setDefaultHandler("guttermousedown",function(r){if(!t.isFocused()||r.getButton()!=0)return;var i=n.getRegion(r);if(i=="foldWidgets")return;var s=r.getDocumentPosition().row,o=t.session.selection;if(r.getShiftKey())o.selectTo(s,0);else{if(r.domEvent.detail==2)return t.selectAll(),r.preventDefault();e.$clickSelection=t.selection.getLineRange(s)}return e.setState("selectByLines"),e.captureMouse(r),r.preventDefault()});var i,u;e.editor.setDefaultHandler("guttermousemove",function(t){var n=t.domEvent.target||t.domEvent.srcElement;if(s.hasCssClass(n,"ace_fold-widget"))return f();r.isOpen&&e.$tooltipFollowsMouse&&c(t),u=t;if(i)return;i=setTimeout(function(){i=null,u&&!e.isMousePressed?a():f()},50)}),o.addListener(t.renderer.$gutter,"mouseout",function(e){u=null;if(!r.isOpen||i)return;i=setTimeout(function(){i=null,f()},50)},t),t.on("changeSession",f),t.on("input",f)}var r=this&&this.__extends||function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},e(t,n)};return function(t,n){function r(){this.constructor=t}if(typeof n!="function"&&n!==null)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");e(t,n),t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),i=this&&this.__values||function(e){var t=typeof Symbol=="function"&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&typeof e.length=="number")return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},s=e("../lib/dom"),o=e("../lib/event"),u=e("../tooltip").Tooltip,a=e("../config").nls;t.GutterHandler=f;var l=function(e){function t(t){var n=e.call(this,t.container)||this;return n.editor=t,n}return r(t,e),t.prototype.setPosition=function(e,t){var n=window.innerWidth||document.documentElement.clientWidth,r=window.innerHeight||document.documentElement.clientHeight,i=this.getWidth(),s=this.getHeight();e+=15,t+=15,e+i>n&&(e-=e+i-n),t+s>r&&(t-=20+s),u.prototype.setPosition.call(this,e,t)},Object.defineProperty(t,"annotationLabels",{get:function(){return{error:{singular:a("error"),plural:a("errors")},warning:{singular:a("warning"),plural:a("warnings")},info:{singular:a("information message"),plural:a("information messages")}}},enumerable:!1,configurable:!0}),t.prototype.showTooltip=function(e){var n=this.editor.renderer.$gutterLayer,r=n.$annotations[e],i;r?i={text:Array.from(r.text),type:Array.from(r.type)}:i={text:[],type:[]};var s=n.session.getFoldLine(e);if(s&&n.$showFoldedAnnotations){var o={error:[],warning:[],info:[]},u;for(var a=e+1;a<=s.end.row;a++){if(!n.$annotations[a])continue;for(var f=0;f ").concat(i.text[a]);h[i.type[a].replace("_fold","")].push(d)}var v=[].concat(h.error,h.warning,h.info).join("
");this.setHtml(v),this.$element.setAttribute("aria-live","polite"),this.isOpen||(this.setTheme(this.editor.renderer.theme),this.setClassName("ace_gutter-tooltip")),this.show(),this.editor._signal("showGutterTooltip",this)},t.prototype.hideTooltip=function(){this.$element.removeAttribute("aria-live"),this.hide(),this.editor._signal("hideGutterTooltip",this)},t.annotationsToSummaryString=function(e){var n,r,s=[],o=["error","warning","info"];try{for(var u=i(o),a=u.next();!a.done;a=u.next()){var f=a.value;if(!e[f].length)continue;var l=e[f].length===1?t.annotationLabels[f].singular:t.annotationLabels[f].plural;s.push("".concat(e[f].length," ").concat(l))}}catch(c){n={error:c}}finally{try{a&&!a.done&&(r=u.return)&&r.call(u)}finally{if(n)throw n.error}}return s.join(", ")},t}(u);t.GutterTooltip=l}),define("ace/mouse/mouse_event",["require","exports","module","ace/lib/event","ace/lib/useragent"],function(e,t,n){"use strict";var r=e("../lib/event"),i=e("../lib/useragent"),s=function(){function e(e,t){this.speed,this.wheelX,this.wheelY,this.domEvent=e,this.editor=t,this.x=this.clientX=e.clientX,this.y=this.clientY=e.clientY,this.$pos=null,this.$inSelection=null,this.propagationStopped=!1,this.defaultPrevented=!1}return e.prototype.stopPropagation=function(){r.stopPropagation(this.domEvent),this.propagationStopped=!0},e.prototype.preventDefault=function(){r.preventDefault(this.domEvent),this.defaultPrevented=!0},e.prototype.stop=function(){this.stopPropagation(),this.preventDefault()},e.prototype.getDocumentPosition=function(){return this.$pos?this.$pos:(this.$pos=this.editor.renderer.screenToTextCoordinates(this.clientX,this.clientY),this.$pos)},e.prototype.getGutterRow=function(){var e=this.getDocumentPosition().row,t=this.editor.session.documentToScreenRow(e,0),n=this.editor.session.documentToScreenRow(this.editor.renderer.$gutterLayer.$lines.get(0).row,0);return t-n},e.prototype.inSelection=function(){if(this.$inSelection!==null)return this.$inSelection;var e=this.editor,t=e.getSelectionRange();if(t.isEmpty())this.$inSelection=!1;else{var n=this.getDocumentPosition();this.$inSelection=t.contains(n.row,n.column)}return this.$inSelection},e.prototype.getButton=function(){return r.getButton(this.domEvent)},e.prototype.getShiftKey=function(){return this.domEvent.shiftKey},e.prototype.getAccelKey=function(){return i.isMac?this.domEvent.metaKey:this.domEvent.ctrlKey},e}();t.MouseEvent=s}),define("ace/mouse/dragdrop_handler",["require","exports","module","ace/lib/dom","ace/lib/event","ace/lib/useragent"],function(e,t,n){"use strict";function f(e){function T(e,n){var r=Date.now(),i=!n||e.row!=n.row,s=!n||e.column!=n.column;if(!S||i||s)t.moveCursorToPosition(e),S=r,x={x:p,y:d};else{var o=l(x.x,x.y,p,d);o>a?S=null:r-S>=u&&(t.renderer.scrollCursorIntoView(),S=null)}}function N(e,n){var r=Date.now(),i=t.renderer.layerConfig.lineHeight,s=t.renderer.layerConfig.characterWidth,u=t.renderer.scroller.getBoundingClientRect(),a={x:{left:p-u.left,right:u.right-p},y:{top:d-u.top,bottom:u.bottom-d}},f=Math.min(a.x.left,a.x.right),l=Math.min(a.y.top,a.y.bottom),c={row:e.row,column:e.column};f/s<=2&&(c.column+=a.x.left=o&&t.renderer.scrollCursorIntoView(c):E=r:E=null}function C(){var e=g;g=t.renderer.screenToTextCoordinates(p,d),T(g,e),N(g,e)}function k(){m=t.selection.toOrientedRange(),h=t.session.addMarker(m,"ace_selection",t.getSelectionStyle()),t.clearSelection(),t.isFocused()&&t.renderer.$cursorLayer.setBlinking(!1),clearInterval(v),C(),v=setInterval(C,20),y=0,i.addListener(document,"mousemove",O)}function L(){clearInterval(v),t.session.removeMarker(h),h=null,t.selection.fromOrientedRange(m),t.isFocused()&&!w&&t.$resetCursorStyle(),m=null,g=null,y=0,E=null,S=null,i.removeListener(document,"mousemove",O)}function O(){A==null&&(A=setTimeout(function(){A!=null&&h&&L()},20))}function M(e){var t=e.types;return!t||Array.prototype.some.call(t,function(e){return e=="text/plain"||e=="Text"})}function _(e){var t=["copy","copymove","all","uninitialized"],n=["move","copymove","linkmove","all","uninitialized"],r=s.isMac?e.altKey:e.ctrlKey,i="uninitialized";try{i=e.dataTransfer.effectAllowed.toLowerCase()}catch(e){}var o="none";return r&&t.indexOf(i)>=0?o="copy":n.indexOf(i)>=0?o="move":t.indexOf(i)>=0&&(o="copy"),o}var t=e.editor,n=r.createElement("div");n.style.cssText="top:-100px;position:absolute;z-index:2147483647;opacity:0.5",n.textContent="\u00a0";var f=["dragWait","dragWaitEnd","startDrag","dragReadyEnd","onMouseDrag"];f.forEach(function(t){e[t]=this[t]},this),t.on("mousedown",this.onMouseDown.bind(e));var c=t.container,h,p,d,v,m,g,y=0,b,w,E,S,x;this.onDragStart=function(e){if(this.cancelDrag||!c.draggable){var r=this;return setTimeout(function(){r.startSelect(),r.captureMouse(e)},0),e.preventDefault()}m=t.getSelectionRange();var i=e.dataTransfer;i.effectAllowed=t.getReadOnly()?"copy":"copyMove",t.container.appendChild(n),i.setDragImage&&i.setDragImage(n,0,0),setTimeout(function(){t.container.removeChild(n)}),i.clearData(),i.setData("Text",t.session.getTextRange()),w=!0,this.setState("drag")},this.onDragEnd=function(e){c.draggable=!1,w=!1,this.setState(null);if(!t.getReadOnly()){var n=e.dataTransfer.dropEffect;!b&&n=="move"&&t.session.remove(t.getSelectionRange()),t.$resetCursorStyle()}this.editor.unsetStyle("ace_dragging"),this.editor.renderer.setCursorStyle("")},this.onDragEnter=function(e){if(t.getReadOnly()||!M(e.dataTransfer))return;return p=e.clientX,d=e.clientY,h||k(),y++,e.dataTransfer.dropEffect=b=_(e),i.preventDefault(e)},this.onDragOver=function(e){if(t.getReadOnly()||!M(e.dataTransfer))return;return p=e.clientX,d=e.clientY,h||(k(),y++),A!==null&&(A=null),e.dataTransfer.dropEffect=b=_(e),i.preventDefault(e)},this.onDragLeave=function(e){y--;if(y<=0&&h)return L(),b=null,i.preventDefault(e)},this.onDrop=function(e){if(!g)return;var n=e.dataTransfer;if(w)switch(b){case"move":m.contains(g.row,g.column)?m={start:g,end:g}:m=t.moveText(m,g);break;case"copy":m=t.moveText(m,g,!0)}else{var r=n.getData("Text");m={start:g,end:t.session.insert(g,r)},t.focus(),b=null}return L(),i.preventDefault(e)},i.addListener(c,"dragstart",this.onDragStart.bind(e),t),i.addListener(c,"dragend",this.onDragEnd.bind(e),t),i.addListener(c,"dragenter",this.onDragEnter.bind(e),t),i.addListener(c,"dragover",this.onDragOver.bind(e),t),i.addListener(c,"dragleave",this.onDragLeave.bind(e),t),i.addListener(c,"drop",this.onDrop.bind(e),t);var A=null}function l(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}var r=e("../lib/dom"),i=e("../lib/event"),s=e("../lib/useragent"),o=200,u=200,a=5;(function(){this.dragWait=function(){var e=Date.now()-this.mousedownEvent.time;e>this.editor.getDragDelay()&&this.startDrag()},this.dragWaitEnd=function(){var e=this.editor.container;e.draggable=!1,this.startSelect(this.mousedownEvent.getDocumentPosition()),this.selectEnd()},this.dragReadyEnd=function(e){this.editor.$resetCursorStyle(),this.editor.unsetStyle("ace_dragging"),this.editor.renderer.setCursorStyle(""),this.dragWaitEnd()},this.startDrag=function(){this.cancelDrag=!1;var e=this.editor,t=e.container;t.draggable=!0,e.renderer.$cursorLayer.setBlinking(!1),e.setStyle("ace_dragging");var n=s.isWin?"default":"move";e.renderer.setCursorStyle(n),this.setState("dragReady")},this.onMouseDrag=function(e){var t=this.editor.container;if(s.isIE&&this.state=="dragReady"){var n=l(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y);n>3&&t.dragDrop()}if(this.state==="dragWait"){var n=l(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y);n>0&&(t.draggable=!1,this.startSelect(this.mousedownEvent.getDocumentPosition()))}},this.onMouseDown=function(e){if(!this.$dragEnabled)return;this.mousedownEvent=e;var t=this.editor,n=e.inSelection(),r=e.getButton(),i=e.domEvent.detail||1;if(i===1&&r===0&&n){if(e.editor.inMultiSelectMode&&(e.getAccelKey()||e.getShiftKey()))return;this.mousedownEvent.time=Date.now();var o=e.domEvent.target||e.domEvent.srcElement;"unselectable"in o&&(o.unselectable="on");if(t.getDragDelay()){if(s.isWebKit){this.cancelDrag=!0;var u=t.container;u.draggable=!0}this.setState("dragWait")}else this.startDrag();this.captureMouse(e,this.onMouseDrag.bind(this)),e.defaultPrevented=!0}}}).call(f.prototype),t.DragdropHandler=f}),define("ace/mouse/touch_handler",["require","exports","module","ace/mouse/mouse_event","ace/lib/event","ace/lib/dom"],function(e,t,n){"use strict";var r=e("./mouse_event").MouseEvent,i=e("../lib/event"),s=e("../lib/dom");t.addTouchListeners=function(e,t){function b(){var e=window.navigator&&window.navigator.clipboard,r=!1,i=function(){var n=t.getCopyText(),i=t.session.getUndoManager().hasUndo();y.replaceChild(s.buildDom(r?["span",!n&&["span",{"class":"ace_mobile-button",action:"selectall"},"Select All"],n&&["span",{"class":"ace_mobile-button",action:"copy"},"Copy"],n&&["span",{"class":"ace_mobile-button",action:"cut"},"Cut"],e&&["span",{"class":"ace_mobile-button",action:"paste"},"Paste"],i&&["span",{"class":"ace_mobile-button",action:"undo"},"Undo"],["span",{"class":"ace_mobile-button",action:"find"},"Find"],["span",{"class":"ace_mobile-button",action:"openCommandPalette"},"Palette"]]:["span"]),y.firstChild)},o=function(n){var s=n.target.getAttribute("action");if(s=="more"||!r)return r=!r,i();if(s=="paste")e.readText().then(function(e){t.execCommand(s,e)});else if(s){if(s=="cut"||s=="copy")e?e.writeText(t.getCopyText()):document.execCommand("copy");t.execCommand(s)}y.firstChild.style.display="none",r=!1,s!="openCommandPalette"&&t.focus()};y=s.buildDom(["div",{"class":"ace_mobile-menu",ontouchstart:function(e){n="menu",e.stopPropagation(),e.preventDefault(),t.textInput.focus()},ontouchend:function(e){e.stopPropagation(),e.preventDefault(),o(e)},onclick:o},["span"],["span",{"class":"ace_mobile-button",action:"more"},"..."]],t.container)}function w(){y||b();var e=t.selection.cursor,n=t.renderer.textToScreenCoordinates(e.row,e.column),r=t.renderer.textToScreenCoordinates(0,0).pageX,i=t.renderer.scrollLeft,s=t.container.getBoundingClientRect();y.style.top=n.pageY-s.top-3+"px",n.pageX-s.left=2?t.selection.getLineRange(p.row):t.session.getBracketRange(p);e&&!e.isEmpty()?t.selection.setRange(e):t.selection.selectWord(),n="wait"}function T(){h+=60,c=setInterval(function(){h--<=0&&(clearInterval(c),c=null),Math.abs(v)<.01&&(v=0),Math.abs(m)<.01&&(m=0),h<20&&(v=.9*v),h<20&&(m=.9*m);var e=t.session.getScrollTop();t.renderer.scrollBy(10*v,10*m),e==t.session.getScrollTop()&&(h=0)},10)}var n="scroll",o,u,a,f,l,c,h=0,p,d=0,v=0,m=0,g,y;i.addListener(e,"contextmenu",function(e){if(!g)return;var n=t.textInput.getElement();n.focus()},t),i.addListener(e,"touchstart",function(e){var i=e.touches;if(l||i.length>1){clearTimeout(l),l=null,a=-1,n="zoom";return}g=t.$mouseHandler.isMousePressed=!0;var s=t.renderer.layerConfig.lineHeight,c=t.renderer.layerConfig.lineHeight,y=e.timeStamp;f=y;var b=i[0],w=b.clientX,E=b.clientY;Math.abs(o-w)+Math.abs(u-E)>s&&(a=-1),o=e.clientX=w,u=e.clientY=E,v=m=0;var T=new r(e,t);p=T.getDocumentPosition();if(y-a<500&&i.length==1&&!h)d++,e.preventDefault(),e.button=0,x();else{d=0;var N=t.selection.cursor,C=t.selection.isEmpty()?N:t.selection.anchor,k=t.renderer.$cursorLayer.getPixelPosition(N,!0),L=t.renderer.$cursorLayer.getPixelPosition(C,!0),A=t.renderer.scroller.getBoundingClientRect(),O=t.renderer.layerConfig.offset,M=t.renderer.scrollLeft,_=function(e,t){return e/=c,t=t/s-.75,e*e+t*t};if(e.clientXP?"cursor":"anchor"),P<3.5?n="anchor":D<3.5?n="cursor":n="scroll",l=setTimeout(S,450)}a=y},t),i.addListener(e,"touchend",function(e){g=t.$mouseHandler.isMousePressed=!1,c&&clearInterval(c),n=="zoom"?(n="",h=0):l?(t.selection.moveToPosition(p),h=0,w()):n=="scroll"?(T(),E()):w(),clearTimeout(l),l=null},t),i.addListener(e,"touchmove",function(e){l&&(clearTimeout(l),l=null);var i=e.touches;if(i.length>1||n=="zoom")return;var s=i[0],a=o-s.clientX,c=u-s.clientY;if(n=="wait"){if(!(a*a+c*c>4))return e.preventDefault();n="cursor"}o=s.clientX,u=s.clientY,e.clientX=s.clientX,e.clientY=s.clientY;var h=e.timeStamp,p=h-f;f=h;if(n=="scroll"){var d=new r(e,t);d.speed=1,d.wheelX=a,d.wheelY=c,10*Math.abs(a)0)if(g==16){for(w=b;w-1){for(w=b;w=0;C--){if(r[C]!=N)break;t[C]=s}}}function I(e,t,n){if(o=e){u=i+1;while(u=e)u++;for(a=i,l=u-1;a=t.length||(o=n[r-1])!=b&&o!=w||(c=t[r+1])!=b&&c!=w)return E;return u&&(c=w),c==o?c:E;case k:o=r>0?n[r-1]:S;if(o==b&&r+10&&n[r-1]==b)return b;if(u)return E;p=r+1,h=t.length;while(p=1425&&d<=2303||d==64286;o=t[p];if(v&&(o==y||o==T))return y}if(r<1||(o=t[r-1])==S)return E;return n[r-1];case S:return u=!1,f=!0,s;case x:return l=!0,E;case O:case M:case D:case P:case _:u=!1;case H:return E}}function R(e){var t=e.charCodeAt(0),n=t>>8;return n==0?t>191?g:B[t]:n==5?/[\u0591-\u05f4]/.test(e)?y:g:n==6?/[\u0610-\u061a\u064b-\u065f\u06d6-\u06e4\u06e7-\u06ed]/.test(e)?A:/[\u0660-\u0669\u066b-\u066c]/.test(e)?w:t==1642?L:/[\u06f0-\u06f9]/.test(e)?b:T:n==32&&t<=8287?j[t&255]:n==254?t>=65136?T:E:E}function U(e){return e>="\u064b"&&e<="\u0655"}var r=["\u0621","\u0641"],i=["\u063a","\u064a"],s=0,o=0,u=!1,a=!1,f=!1,l=!1,c=!1,h=!1,p=[[0,3,0,1,0,0,0],[0,3,0,1,2,2,0],[0,3,0,17,2,0,1],[0,3,5,5,4,1,0],[0,3,21,21,4,0,1],[0,3,5,5,4,2,0]],d=[[2,0,1,1,0,1,0],[2,0,1,1,0,2,0],[2,0,2,1,3,2,0],[2,0,2,33,3,1,1]],v=0,m=1,g=0,y=1,b=2,w=3,E=4,S=5,x=6,T=7,N=8,C=9,k=10,L=11,A=12,O=13,M=14,_=15,D=16,P=17,H=18,B=[H,H,H,H,H,H,H,H,H,x,S,x,N,S,H,H,H,H,H,H,H,H,H,H,H,H,H,H,S,S,S,x,N,E,E,L,L,L,E,E,E,E,E,k,C,k,C,C,b,b,b,b,b,b,b,b,b,b,C,E,E,E,E,E,E,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,E,E,E,E,E,E,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,E,E,E,E,H,H,H,H,H,H,S,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,C,E,L,L,L,L,E,E,E,E,g,E,E,H,E,E,L,L,b,b,E,g,E,E,E,b,g,E,E,E,E,E],j=[N,N,N,N,N,N,N,N,N,N,N,H,H,H,g,y,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,N,S,O,M,_,D,P,C,L,L,L,L,L,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,C,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,N];t.L=g,t.R=y,t.EN=b,t.ON_R=3,t.AN=4,t.R_H=5,t.B=6,t.RLE=7,t.DOT="\u00b7",t.doBidiReorder=function(e,n,r){if(e.length<2)return{};var i=e.split(""),o=new Array(i.length),u=new Array(i.length),a=[];s=r?m:v,F(i,a,i.length,n);for(var f=0;fT&&n[f]0&&i[f-1]==="\u0644"&&/\u0622|\u0623|\u0625|\u0627/.test(i[f])&&(a[f-1]=a[f]=t.R_H,f++);i[i.length-1]===t.DOT&&(a[i.length-1]=t.B),i[0]==="\u202b"&&(a[0]=t.RLE);for(var f=0;f=0&&(e=this.session.$docRowCache[n])}return e},e.prototype.getSplitIndex=function(){var e=0,t=this.session.$screenRowCache;if(t.length){var n,r=this.session.$getRowCacheIndex(t,this.currentRow);while(this.currentRow-e>0){n=this.session.$getRowCacheIndex(t,this.currentRow-e-1);if(n!==r)break;r=n,e++}}else e=this.currentRow;return e},e.prototype.updateRowLine=function(e,t){e===undefined&&(e=this.getDocumentRow());var n=e===this.session.getLength()-1,s=n?this.EOF:this.EOL;this.wrapIndent=0,this.line=this.session.getLine(e),this.isRtlDir=this.$isRtl||this.line.charAt(0)===this.RLE;if(this.session.$useWrapMode){var o=this.session.$wrapData[e];o&&(t===undefined&&(t=this.getSplitIndex()),t>0&&o.length?(this.wrapIndent=o.indent,this.wrapOffset=this.wrapIndent*this.charWidths[r.L],this.line=tt?this.session.getOverwrite()?e:e-1:t,i=r.getVisualFromLogicalIdx(n,this.bidiMap),s=this.bidiMap.bidiLevels,o=0;!this.session.getOverwrite()&&e<=t&&s[i]%2!==0&&i++;for(var u=0;ut&&s[i]%2===0&&(o+=this.charWidths[s[i]]),this.wrapIndent&&(o+=this.isRtlDir?-1*this.wrapOffset:this.wrapOffset),this.isRtlDir&&(o+=this.rtlLineOffset),o},e.prototype.getSelections=function(e,t){var n=this.bidiMap,r=n.bidiLevels,i,s=[],o=0,u=Math.min(e,t)-this.wrapIndent,a=Math.max(e,t)-this.wrapIndent,f=!1,l=!1,c=0;this.wrapIndent&&(o+=this.isRtlDir?-1*this.wrapOffset:this.wrapOffset);for(var h,p=0;p=u&&hn+s/2){n+=s;if(r===i.length-1){s=0;break}s=this.charWidths[i[++r]]}return r>0&&i[r-1]%2!==0&&i[r]%2===0?(e0&&i[r-1]%2===0&&i[r]%2!==0?t=1+(e>n?this.bidiMap.logicalFromVisual[r]:this.bidiMap.logicalFromVisual[r-1]):this.isRtlDir&&r===i.length-1&&s===0&&i[r-1]%2===0||!this.isRtlDir&&r===0&&i[r]%2!==0?t=1+this.bidiMap.logicalFromVisual[r]:(r>0&&i[r-1]%2!==0&&s!==0&&r--,t=this.bidiMap.logicalFromVisual[r]),t===0&&this.isRtlDir&&t++,t+this.wrapIndent},e}();t.BidiHandler=o}),define("ace/selection",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/lib/event_emitter","ace/range"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/lang"),s=e("./lib/event_emitter").EventEmitter,o=e("./range").Range,u=function(){function e(e){this.session=e,this.doc=e.getDocument(),this.clearSelection(),this.cursor=this.lead=this.doc.createAnchor(0,0),this.anchor=this.doc.createAnchor(0,0),this.$silent=!1;var t=this;this.cursor.on("change",function(e){t.$cursorChanged=!0,t.$silent||t._emit("changeCursor"),!t.$isEmpty&&!t.$silent&&t._emit("changeSelection"),!t.$keepDesiredColumnOnChange&&e.old.column!=e.value.column&&(t.$desiredColumn=null)}),this.anchor.on("change",function(){t.$anchorChanged=!0,!t.$isEmpty&&!t.$silent&&t._emit("changeSelection")})}return e.prototype.isEmpty=function(){return this.$isEmpty||this.anchor.row==this.lead.row&&this.anchor.column==this.lead.column},e.prototype.isMultiLine=function(){return!this.$isEmpty&&this.anchor.row!=this.cursor.row},e.prototype.getCursor=function(){return this.lead.getPosition()},e.prototype.setAnchor=function(e,t){this.$isEmpty=!1,this.anchor.setPosition(e,t)},e.prototype.getAnchor=function(){return this.$isEmpty?this.getSelectionLead():this.anchor.getPosition()},e.prototype.getSelectionLead=function(){return this.lead.getPosition()},e.prototype.isBackwards=function(){var e=this.anchor,t=this.lead;return e.row>t.row||e.row==t.row&&e.column>t.column},e.prototype.getRange=function(){var e=this.anchor,t=this.lead;return this.$isEmpty?o.fromPoints(t,t):this.isBackwards()?o.fromPoints(t,e):o.fromPoints(e,t)},e.prototype.clearSelection=function(){this.$isEmpty||(this.$isEmpty=!0,this._emit("changeSelection"))},e.prototype.selectAll=function(){this.$setSelection(0,0,Number.MAX_VALUE,Number.MAX_VALUE)},e.prototype.setRange=function(e,t){var n=t?e.end:e.start,r=t?e.start:e.end;this.$setSelection(n.row,n.column,r.row,r.column)},e.prototype.$setSelection=function(e,t,n,r){if(this.$silent)return;var i=this.$isEmpty,s=this.inMultiSelectMode;this.$silent=!0,this.$cursorChanged=this.$anchorChanged=!1,this.anchor.setPosition(e,t),this.cursor.setPosition(n,r),this.$isEmpty=!o.comparePoints(this.anchor,this.cursor),this.$silent=!1,this.$cursorChanged&&this._emit("changeCursor"),(this.$cursorChanged||this.$anchorChanged||i!=this.$isEmpty||s)&&this._emit("changeSelection")},e.prototype.$moveSelection=function(e){var t=this.lead;this.$isEmpty&&this.setSelectionAnchor(t.row,t.column),e.call(this)},e.prototype.selectTo=function(e,t){this.$moveSelection(function(){this.moveCursorTo(e,t)})},e.prototype.selectToPosition=function(e){this.$moveSelection(function(){this.moveCursorToPosition(e)})},e.prototype.moveTo=function(e,t){this.clearSelection(),this.moveCursorTo(e,t)},e.prototype.moveToPosition=function(e){this.clearSelection(),this.moveCursorToPosition(e)},e.prototype.selectUp=function(){this.$moveSelection(this.moveCursorUp)},e.prototype.selectDown=function(){this.$moveSelection(this.moveCursorDown)},e.prototype.selectRight=function(){this.$moveSelection(this.moveCursorRight)},e.prototype.selectLeft=function(){this.$moveSelection(this.moveCursorLeft)},e.prototype.selectLineStart=function(){this.$moveSelection(this.moveCursorLineStart)},e.prototype.selectLineEnd=function(){this.$moveSelection(this.moveCursorLineEnd)},e.prototype.selectFileEnd=function(){this.$moveSelection(this.moveCursorFileEnd)},e.prototype.selectFileStart=function(){this.$moveSelection(this.moveCursorFileStart)},e.prototype.selectWordRight=function(){this.$moveSelection(this.moveCursorWordRight)},e.prototype.selectWordLeft=function(){this.$moveSelection(this.moveCursorWordLeft)},e.prototype.getWordRange=function(e,t){if(typeof t=="undefined"){var n=e||this.lead;e=n.row,t=n.column}return this.session.getWordRange(e,t)},e.prototype.selectWord=function(){this.setSelectionRange(this.getWordRange())},e.prototype.selectAWord=function(){var e=this.getCursor(),t=this.session.getAWordRange(e.row,e.column);this.setSelectionRange(t)},e.prototype.getLineRange=function(e,t){var n=typeof e=="number"?e:this.lead.row,r,i=this.session.getFoldLine(n);return i?(n=i.start.row,r=i.end.row):r=n,t===!0?new o(n,0,r,this.session.getLine(r).length):new o(n,0,r+1,0)},e.prototype.selectLine=function(){this.setSelectionRange(this.getLineRange())},e.prototype.moveCursorUp=function(){this.moveCursorBy(-1,0)},e.prototype.moveCursorDown=function(){this.moveCursorBy(1,0)},e.prototype.wouldMoveIntoSoftTab=function(e,t,n){var r=e.column,i=e.column+t;return n<0&&(r=e.column-t,i=e.column),this.session.isTabStop(e)&&this.doc.getLine(e.row).slice(r,i).split(" ").length-1==t},e.prototype.moveCursorLeft=function(){var e=this.lead.getPosition(),t;if(t=this.session.getFoldAt(e.row,e.column,-1))this.moveCursorTo(t.start.row,t.start.column);else if(e.column===0)e.row>0&&this.moveCursorTo(e.row-1,this.doc.getLine(e.row-1).length);else{var n=this.session.getTabSize();this.wouldMoveIntoSoftTab(e,n,-1)&&!this.session.getNavigateWithinSoftTabs()?this.moveCursorBy(0,-n):this.moveCursorBy(0,-1)}},e.prototype.moveCursorRight=function(){var e=this.lead.getPosition(),t;if(t=this.session.getFoldAt(e.row,e.column,1))this.moveCursorTo(t.end.row,t.end.column);else if(this.lead.column==this.doc.getLine(this.lead.row).length)this.lead.row0&&(t.column=r)}}this.moveCursorTo(t.row,t.column)},e.prototype.moveCursorFileEnd=function(){var e=this.doc.getLength()-1,t=this.doc.getLine(e).length;this.moveCursorTo(e,t)},e.prototype.moveCursorFileStart=function(){this.moveCursorTo(0,0)},e.prototype.moveCursorLongWordRight=function(){var e=this.lead.row,t=this.lead.column,n=this.doc.getLine(e),r=n.substring(t);this.session.nonTokenRe.lastIndex=0,this.session.tokenRe.lastIndex=0;var i=this.session.getFoldAt(e,t,1);if(i){this.moveCursorTo(i.end.row,i.end.column);return}this.session.nonTokenRe.exec(r)&&(t+=this.session.nonTokenRe.lastIndex,this.session.nonTokenRe.lastIndex=0,r=n.substring(t));if(t>=n.length){this.moveCursorTo(e,n.length),this.moveCursorRight(),e0&&this.moveCursorWordLeft();return}this.session.tokenRe.exec(s)&&(t-=this.session.tokenRe.lastIndex,this.session.tokenRe.lastIndex=0),this.moveCursorTo(e,t)},e.prototype.$shortWordEndIndex=function(e){var t=0,n,r=/\s/,i=this.session.tokenRe;i.lastIndex=0;if(this.session.tokenRe.exec(e))t=this.session.tokenRe.lastIndex;else{while((n=e[t])&&r.test(n))t++;if(t<1){i.lastIndex=0;while((n=e[t])&&!i.test(n)){i.lastIndex=0,t++;if(r.test(n)){if(t>2){t--;break}while((n=e[t])&&r.test(n))t++;if(t>2)break}}}}return i.lastIndex=0,t},e.prototype.moveCursorShortWordRight=function(){var e=this.lead.row,t=this.lead.column,n=this.doc.getLine(e),r=n.substring(t),i=this.session.getFoldAt(e,t,1);if(i)return this.moveCursorTo(i.end.row,i.end.column);if(t==n.length){var s=this.doc.getLength();do e++,r=this.doc.getLine(e);while(e0&&/^\s*$/.test(r));t=r.length,/\s+$/.test(r)||(r="")}var s=i.stringReverse(r),o=this.$shortWordEndIndex(s);return this.moveCursorTo(e,t-o)},e.prototype.moveCursorWordRight=function(){this.session.$selectLongWords?this.moveCursorLongWordRight():this.moveCursorShortWordRight()},e.prototype.moveCursorWordLeft=function(){this.session.$selectLongWords?this.moveCursorLongWordLeft():this.moveCursorShortWordLeft()},e.prototype.moveCursorBy=function(e,t){var n=this.session.documentToScreenPosition(this.lead.row,this.lead.column),r;t===0&&(e!==0&&(this.session.$bidiHandler.isBidiRow(n.row,this.lead.row)?(r=this.session.$bidiHandler.getPosLeft(n.column),n.column=Math.round(r/this.session.$bidiHandler.charWidths[0])):r=n.column*this.session.$bidiHandler.charWidths[0]),this.$desiredColumn?n.column=this.$desiredColumn:this.$desiredColumn=n.column);if(e!=0&&this.session.lineWidgets&&this.session.lineWidgets[this.lead.row]){var i=this.session.lineWidgets[this.lead.row];e<0?e-=i.rowsAbove||0:e>0&&(e+=i.rowCount-(i.rowsAbove||0))}var s=this.session.screenToDocumentPosition(n.row+e,n.column,r);e!==0&&t===0&&s.row===this.lead.row&&s.column===this.lead.column,this.moveCursorTo(s.row,s.column+t,t===0)},e.prototype.moveCursorToPosition=function(e){this.moveCursorTo(e.row,e.column)},e.prototype.moveCursorTo=function(e,t,n){var r=this.session.getFoldAt(e,t,1);r&&(e=r.start.row,t=r.start.column),this.$keepDesiredColumnOnChange=!0;var i=this.session.getLine(e);/[\uDC00-\uDFFF]/.test(i.charAt(t))&&i.charAt(t-1)&&(this.lead.row==e&&this.lead.column==t+1?t-=1:t+=1),this.lead.setPosition(e,t),this.$keepDesiredColumnOnChange=!1,n||(this.$desiredColumn=null)},e.prototype.moveCursorToScreen=function(e,t,n){var r=this.session.screenToDocumentPosition(e,t);this.moveCursorTo(r.row,r.column,n)},e.prototype.detach=function(){this.lead.detach(),this.anchor.detach()},e.prototype.fromOrientedRange=function(e){this.setSelectionRange(e,e.cursor==e.start),this.$desiredColumn=e.desiredColumn||this.$desiredColumn},e.prototype.toOrientedRange=function(e){var t=this.getRange();return e?(e.start.column=t.start.column,e.start.row=t.start.row,e.end.column=t.end.column,e.end.row=t.end.row):e=t,e.cursor=this.isBackwards()?e.start:e.end,e.desiredColumn=this.$desiredColumn,e},e.prototype.getRangeOfMovements=function(e){var t=this.getCursor();try{e(this);var n=this.getCursor();return o.fromPoints(t,n)}catch(r){return o.fromPoints(t,t)}finally{this.moveCursorToPosition(t)}},e.prototype.toJSON=function(){if(this.rangeCount)var e=this.ranges.map(function(e){var t=e.clone();return t.isBackwards=e.cursor==e.start,t});else{var e=this.getRange();e.isBackwards=this.isBackwards()}return e},e.prototype.fromJSON=function(e){if(e.start==undefined){if(this.rangeList&&e.length>1){this.toSingleRange(e[0]);for(var t=e.length;t--;){var n=o.fromPoints(e[t].start,e[t].end);e[t].isBackwards&&(n.cursor=n.start),this.addRange(n,!0)}return}e=e[0]}this.rangeList&&this.toSingleRange(e),this.setSelectionRange(e,e.isBackwards)},e.prototype.isEqual=function(e){if((e.length||this.rangeCount)&&e.length!=this.rangeCount)return!1;if(!e.length||!this.ranges)return this.getRange().isEqual(e);for(var t=this.ranges.length;t--;)if(!this.ranges[t].isEqual(e[t]))return!1;return!0},e}();u.prototype.setSelectionAnchor=u.prototype.setAnchor,u.prototype.getSelectionAnchor=u.prototype.getAnchor,u.prototype.setSelectionRange=u.prototype.setRange,r.implement(u.prototype,s),t.Selection=u}),define("ace/tokenizer",["require","exports","module","ace/lib/report_error"],function(e,t,n){"use strict";var r=e("./lib/report_error").reportError,i=2e3,s=function(){function e(e){this.splitRegex,this.states=e,this.regExps={},this.matchMappings={};for(var t in this.states){var n=this.states[t],r=[],i=0,s=this.matchMappings[t]={defaultToken:"text"},o="g",u=[];for(var a=0;a1?f.onMatch=this.$applyToken:f.onMatch=f.token),c>1&&(/\\\d/.test(f.regex)?l=f.regex.replace(/\\([0-9]+)/g,function(e,t){return"\\"+(parseInt(t,10)+i+1)}):(c=1,l=this.removeCapturingGroups(f.regex)),!f.splitRegex&&typeof f.token!="string"&&u.push(f)),s[i]=a,i+=c,r.push(l),f.onMatch||(f.onMatch=null)}r.length||(s[0]=0,r.push("$")),u.forEach(function(e){e.splitRegex=this.createSplitterRegexp(e.regex,o)},this),this.regExps[t]=new RegExp("("+r.join(")|(")+")|($)",o)}}return e.prototype.$setMaxTokenCount=function(e){i=e|0},e.prototype.$applyToken=function(e){var t=this.splitRegex.exec(e).slice(1),n=this.token.apply(this,t);if(typeof n=="string")return[{type:n,value:e}];var r=[];for(var i=0,s=n.length;il){var g=e.substring(l,m-v.length);h.type==p?h.value+=g:(h.type&&f.push(h),h={type:p,value:g})}for(var y=0;yi){c>2*e.length&&this.reportError("infinite loop with in ace tokenizer",{startState:t,line:e});while(l1&&n[0]!==r&&n.unshift("#tmp",r),{tokens:f,state:n.length?n:r}},e}();s.prototype.reportError=r,t.Tokenizer=s}),define("ace/mode/text_highlight_rules",["require","exports","module","ace/lib/deep_copy"],function(e,t,n){"use strict";var r=e("../lib/deep_copy").deepCopy,i;i=function(){this.$rules={start:[{token:"empty_line",regex:"^$"},{defaultToken:"text"}]}},function(){this.addRules=function(e,t){if(!t){for(var n in e)this.$rules[n]=e[n];return}for(var n in e){var r=e[n];for(var i=0;i=this.$rowTokens.length){this.$row+=1,e||(e=this.$session.getLength());if(this.$row>=e)return this.$row=e-1,null;this.$rowTokens=this.$session.getTokens(this.$row),this.$tokenIndex=0}return this.$rowTokens[this.$tokenIndex]},e.prototype.getCurrentToken=function(){return this.$rowTokens[this.$tokenIndex]},e.prototype.getCurrentTokenRow=function(){return this.$row},e.prototype.getCurrentTokenColumn=function(){var e=this.$rowTokens,t=this.$tokenIndex,n=e[t].start;if(n!==undefined)return n;n=0;while(t>0)t-=1,n+=e[t].value.length;return n},e.prototype.getCurrentTokenPosition=function(){return{row:this.$row,column:this.getCurrentTokenColumn()}},e.prototype.getCurrentTokenRange=function(){var e=this.$rowTokens[this.$tokenIndex],t=this.getCurrentTokenColumn();return new r(this.$row,t,this.$row,t+e.value.length)},e}();t.TokenIterator=i}),define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("../../token_iterator").TokenIterator,o=e("../../lib/lang"),u=["text","paren.rparen","rparen","paren","punctuation.operator"],a=["text","paren.rparen","rparen","paren","punctuation.operator","comment"],f,l={},c={'"':'"',"'":"'"},h=function(e){var t=-1;e.multiSelect&&(t=e.selection.index,l.rangeCount!=e.multiSelect.rangeCount&&(l={rangeCount:e.multiSelect.rangeCount}));if(l[t])return f=l[t];f=l[t]={autoInsertedBrackets:0,autoInsertedRow:-1,autoInsertedLineEnd:"",maybeInsertedBrackets:0,maybeInsertedRow:-1,maybeInsertedLineStart:"",maybeInsertedLineEnd:""}},p=function(e,t,n,r){var i=e.end.row-e.start.row;return{text:n+t+r,selection:[0,e.start.column+1,i,e.end.column+(i?0:1)]}},d;d=function(e){e=e||{},this.add("braces","insertion",function(t,n,r,i,s){var u=r.getCursorPosition(),a=i.doc.getLine(u.row);if(s=="{"){h(r);var l=r.getSelectionRange(),c=i.doc.getTextRange(l);if(c!==""&&c!=="{"&&r.getWrapBehavioursEnabled())return p(l,c,"{","}");if(d.isSaneInsertion(r,i))return/[\]\}\)]/.test(a[u.column])||r.inMultiSelectMode||e.braces?(d.recordAutoInsert(r,i,"}"),{text:"{}",selection:[1,1]}):(d.recordMaybeInsert(r,i,"{"),{text:"{",selection:[1,1]})}else if(s=="}"){h(r);var v=a.substring(u.column,u.column+1);if(v=="}"){var m=i.$findOpeningBracket("}",{column:u.column+1,row:u.row});if(m!==null&&d.isAutoInsertedClosing(u,a,s))return d.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}else{if(s=="\n"||s=="\r\n"){h(r);var g="";d.isMaybeInsertedClosing(u,a)&&(g=o.stringRepeat("}",f.maybeInsertedBrackets),d.clearMaybeInsertedClosing());var v=a.substring(u.column,u.column+1);if(v==="}"){var y=i.findMatchingBracket({row:u.row,column:u.column+1},"}");if(!y)return null;var b=this.$getIndent(i.getLine(y.row))}else{if(!g){d.clearMaybeInsertedClosing();return}var b=this.$getIndent(a)}var w=b+i.getTabString();return{text:"\n"+w+"\n"+b+g,selection:[1,w.length,1,w.length]}}d.clearMaybeInsertedClosing()}}),this.add("braces","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="{"){h(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.end.column,i.end.column+1);if(u=="}")return i.end.column++,i;f.maybeInsertedBrackets--}}),this.add("parens","insertion",function(e,t,n,r,i){if(i=="("){h(n);var s=n.getSelectionRange(),o=r.doc.getTextRange(s);if(o!==""&&n.getWrapBehavioursEnabled())return p(s,o,"(",")");if(d.isSaneInsertion(n,r))return d.recordAutoInsert(n,r,")"),{text:"()",selection:[1,1]}}else if(i==")"){h(n);var u=n.getCursorPosition(),a=r.doc.getLine(u.row),f=a.substring(u.column,u.column+1);if(f==")"){var l=r.$findOpeningBracket(")",{column:u.column+1,row:u.row});if(l!==null&&d.isAutoInsertedClosing(u,a,i))return d.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}}),this.add("parens","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="("){h(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u==")")return i.end.column++,i}}),this.add("brackets","insertion",function(e,t,n,r,i){if(i=="["){h(n);var s=n.getSelectionRange(),o=r.doc.getTextRange(s);if(o!==""&&n.getWrapBehavioursEnabled())return p(s,o,"[","]");if(d.isSaneInsertion(n,r))return d.recordAutoInsert(n,r,"]"),{text:"[]",selection:[1,1]}}else if(i=="]"){h(n);var u=n.getCursorPosition(),a=r.doc.getLine(u.row),f=a.substring(u.column,u.column+1);if(f=="]"){var l=r.$findOpeningBracket("]",{column:u.column+1,row:u.row});if(l!==null&&d.isAutoInsertedClosing(u,a,i))return d.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}}),this.add("brackets","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="["){h(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u=="]")return i.end.column++,i}}),this.add("string_dquotes","insertion",function(e,t,n,r,i){var s=r.$mode.$quotes||c;if(i.length==1&&s[i]){if(this.lineCommentStart&&this.lineCommentStart.indexOf(i)!=-1)return;h(n);var o=i,u=n.getSelectionRange(),a=r.doc.getTextRange(u);if(a!==""&&(a.length!=1||!s[a])&&n.getWrapBehavioursEnabled())return p(u,a,o,o);if(!a){var f=n.getCursorPosition(),l=r.doc.getLine(f.row),d=l.substring(f.column-1,f.column),v=l.substring(f.column,f.column+1),m=r.getTokenAt(f.row,f.column),g=r.getTokenAt(f.row,f.column+1);if(d=="\\"&&m&&/escape/.test(m.type))return null;var y=m&&/string|escape/.test(m.type),b=!g||/string|escape/.test(g.type),w;if(v==o)w=y!==b,w&&/string\.end/.test(g.type)&&(w=!1);else{if(y&&!b)return null;if(y&&b)return null;var E=r.$mode.tokenRe;E.lastIndex=0;var S=E.test(d);E.lastIndex=0;var x=E.test(v),T=r.$mode.$pairQuotesAfter,N=T&&T[o]&&T[o].test(d);if(!N&&S||x)return null;if(v&&!/[\s;,.})\]\\]/.test(v))return null;var C=l[f.column-2];if(!(d!=o||C!=o&&!E.test(C)))return null;w=!0}return{text:w?o+o:"",selection:[1,1]}}}}),this.add("string_dquotes","deletion",function(e,t,n,r,i){var s=r.$mode.$quotes||c,o=r.doc.getTextRange(i);if(!i.isMultiLine()&&s.hasOwnProperty(o)){h(n);var u=r.doc.getLine(i.start.row),a=u.substring(i.start.column+1,i.start.column+2);if(a==o)return i.end.column++,i}}),e.closeDocComment!==!1&&this.add("doc comment end","insertion",function(e,t,n,r,i){if(e==="doc-start"&&(i==="\n"||i==="\r\n")&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row),u=r.doc.getLine(s.row+1),a=this.$getIndent(o);if(/\s*\*/.test(u))return/^\s*\*/.test(o)?{text:i+a+"* ",selection:[1,3+a.length,1,3+a.length]}:{text:i+a+" * ",selection:[1,3+a.length,1,3+a.length]};if(/\/\*\*/.test(o.substring(0,s.column)))return{text:i+a+" * "+i+" "+a+"*/",selection:[1,4+a.length,1,4+a.length]}}})},d.isSaneInsertion=function(e,t){var n=e.getCursorPosition(),r=new s(t,n.row,n.column);if(!this.$matchTokenType(r.getCurrentToken()||"text",u)){if(/[)}\]]/.test(e.session.getLine(n.row)[n.column]))return!0;var i=new s(t,n.row,n.column+1);if(!this.$matchTokenType(i.getCurrentToken()||"text",u))return!1}return r.stepForward(),r.getCurrentTokenRow()!==n.row||this.$matchTokenType(r.getCurrentToken()||"text",a)},d.$matchTokenType=function(e,t){return t.indexOf(e.type||e)>-1},d.recordAutoInsert=function(e,t,n){var r=e.getCursorPosition(),i=t.doc.getLine(r.row);this.isAutoInsertedClosing(r,i,f.autoInsertedLineEnd[0])||(f.autoInsertedBrackets=0),f.autoInsertedRow=r.row,f.autoInsertedLineEnd=n+i.substr(r.column),f.autoInsertedBrackets++},d.recordMaybeInsert=function(e,t,n){var r=e.getCursorPosition(),i=t.doc.getLine(r.row);this.isMaybeInsertedClosing(r,i)||(f.maybeInsertedBrackets=0),f.maybeInsertedRow=r.row,f.maybeInsertedLineStart=i.substr(0,r.column)+n,f.maybeInsertedLineEnd=i.substr(r.column),f.maybeInsertedBrackets++},d.isAutoInsertedClosing=function(e,t,n){return f.autoInsertedBrackets>0&&e.row===f.autoInsertedRow&&n===f.autoInsertedLineEnd[0]&&t.substr(e.column)===f.autoInsertedLineEnd},d.isMaybeInsertedClosing=function(e,t){return f.maybeInsertedBrackets>0&&e.row===f.maybeInsertedRow&&t.substr(e.column)===f.maybeInsertedLineEnd&&t.substr(0,e.column)==f.maybeInsertedLineStart},d.popAutoInsertedClosing=function(){f.autoInsertedLineEnd=f.autoInsertedLineEnd.substr(1),f.autoInsertedBrackets--},d.clearMaybeInsertedClosing=function(){f&&(f.maybeInsertedBrackets=0,f.maybeInsertedRow=-1)},r.inherits(d,i),t.CstyleBehaviour=d}),define("ace/unicode",["require","exports","module"],function(e,t,n){"use strict";var r=[48,9,8,25,5,0,2,25,48,0,11,0,5,0,6,22,2,30,2,457,5,11,15,4,8,0,2,0,18,116,2,1,3,3,9,0,2,2,2,0,2,19,2,82,2,138,2,4,3,155,12,37,3,0,8,38,10,44,2,0,2,1,2,1,2,0,9,26,6,2,30,10,7,61,2,9,5,101,2,7,3,9,2,18,3,0,17,58,3,100,15,53,5,0,6,45,211,57,3,18,2,5,3,11,3,9,2,1,7,6,2,2,2,7,3,1,3,21,2,6,2,0,4,3,3,8,3,1,3,3,9,0,5,1,2,4,3,11,16,2,2,5,5,1,3,21,2,6,2,1,2,1,2,1,3,0,2,4,5,1,3,2,4,0,8,3,2,0,8,15,12,2,2,8,2,2,2,21,2,6,2,1,2,4,3,9,2,2,2,2,3,0,16,3,3,9,18,2,2,7,3,1,3,21,2,6,2,1,2,4,3,8,3,1,3,2,9,1,5,1,2,4,3,9,2,0,17,1,2,5,4,2,2,3,4,1,2,0,2,1,4,1,4,2,4,11,5,4,4,2,2,3,3,0,7,0,15,9,18,2,2,7,2,2,2,22,2,9,2,4,4,7,2,2,2,3,8,1,2,1,7,3,3,9,19,1,2,7,2,2,2,22,2,9,2,4,3,8,2,2,2,3,8,1,8,0,2,3,3,9,19,1,2,7,2,2,2,22,2,15,4,7,2,2,2,3,10,0,9,3,3,9,11,5,3,1,2,17,4,23,2,8,2,0,3,6,4,0,5,5,2,0,2,7,19,1,14,57,6,14,2,9,40,1,2,0,3,1,2,0,3,0,7,3,2,6,2,2,2,0,2,0,3,1,2,12,2,2,3,4,2,0,2,5,3,9,3,1,35,0,24,1,7,9,12,0,2,0,2,0,5,9,2,35,5,19,2,5,5,7,2,35,10,0,58,73,7,77,3,37,11,42,2,0,4,328,2,3,3,6,2,0,2,3,3,40,2,3,3,32,2,3,3,6,2,0,2,3,3,14,2,56,2,3,3,66,5,0,33,15,17,84,13,619,3,16,2,25,6,74,22,12,2,6,12,20,12,19,13,12,2,2,2,1,13,51,3,29,4,0,5,1,3,9,34,2,3,9,7,87,9,42,6,69,11,28,4,11,5,11,11,39,3,4,12,43,5,25,7,10,38,27,5,62,2,28,3,10,7,9,14,0,89,75,5,9,18,8,13,42,4,11,71,55,9,9,4,48,83,2,2,30,14,230,23,280,3,5,3,37,3,5,3,7,2,0,2,0,2,0,2,30,3,52,2,6,2,0,4,2,2,6,4,3,3,5,5,12,6,2,2,6,67,1,20,0,29,0,14,0,17,4,60,12,5,0,4,11,18,0,5,0,3,9,2,0,4,4,7,0,2,0,2,0,2,3,2,10,3,3,6,4,5,0,53,1,2684,46,2,46,2,132,7,6,15,37,11,53,10,0,17,22,10,6,2,6,2,6,2,6,2,6,2,6,2,6,2,6,2,31,48,0,470,1,36,5,2,4,6,1,5,85,3,1,3,2,2,89,2,3,6,40,4,93,18,23,57,15,513,6581,75,20939,53,1164,68,45,3,268,4,27,21,31,3,13,13,1,2,24,9,69,11,1,38,8,3,102,3,1,111,44,25,51,13,68,12,9,7,23,4,0,5,45,3,35,13,28,4,64,15,10,39,54,10,13,3,9,7,22,4,1,5,66,25,2,227,42,2,1,3,9,7,11171,13,22,5,48,8453,301,3,61,3,105,39,6,13,4,6,11,2,12,2,4,2,0,2,1,2,1,2,107,34,362,19,63,3,53,41,11,5,15,17,6,13,1,25,2,33,4,2,134,20,9,8,25,5,0,2,25,12,88,4,5,3,5,3,5,3,2],i=0,s=[];for(var o=0;o2?r%f!=f-1:r%f==0}}var E=Infinity;w(function(e,t){var n=e.search(/\S/);n!==-1?(ne.length&&(E=e.length)}),u==Infinity&&(u=E,s=!1,o=!1),l&&u%f!=0&&(u=Math.floor(u/f)*f),w(o?m:v)},this.toggleBlockComment=function(e,t,n,r){var i=this.blockComment;if(!i)return;!i.start&&i[0]&&(i=i[0]);var s=new f(t,r.row,r.column),o=s.getCurrentToken(),u=t.selection,a=t.selection.toOrientedRange(),c,h;if(o&&/comment/.test(o.type)){var p,d;while(o&&/comment/.test(o.type)){var v=o.value.indexOf(i.start);if(v!=-1){var m=s.getCurrentTokenRow(),g=s.getCurrentTokenColumn()+v;p=new l(m,g,m,g+i.start.length);break}o=s.stepBackward()}var s=new f(t,r.row,r.column),o=s.getCurrentToken();while(o&&/comment/.test(o.type)){var v=o.value.indexOf(i.end);if(v!=-1){var m=s.getCurrentTokenRow(),g=s.getCurrentTokenColumn()+v;d=new l(m,g,m,g+i.end.length);break}o=s.stepForward()}d&&t.remove(d),p&&(t.remove(p),c=p.start.row,h=-i.start.length)}else h=i.start.length,c=n.start.row,t.insert(n.end,i.end),t.insert(n.start,i.start);a.start.row==c&&(a.start.column+=h),a.end.row==c&&(a.end.column+=h),t.selection.fromOrientedRange(a)},this.getNextLineIndent=function(e,t,n){return this.$getIndent(t)},this.checkOutdent=function(e,t,n){return!1},this.autoOutdent=function(e,t,n){},this.$getIndent=function(e){return e.match(/^\s*/)[0]},this.createWorker=function(e){return null},this.createModeDelegates=function(e){this.$embeds=[],this.$modes={};for(var t in e)if(e[t]){var n=e[t],i=n.prototype.$id,s=r.$modes[i];s||(r.$modes[i]=s=new n),r.$modes[t]||(r.$modes[t]=s),this.$embeds.push(t),this.$modes[t]=s}var o=["toggleBlockComment","toggleCommentLines","getNextLineIndent","checkOutdent","autoOutdent","transformAction","getCompletions"],u=function(e){(function(t){var n=o[e],r=t[n];t[o[e]]=function(){return this.$delegator(n,arguments,r)}})(a)},a=this;for(var t=0;t=0&&t.row=0&&t.column<=e[t.row].length}function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.action must be 'insert' or 'remove'"),t.lines instanceof Array||r(t,"delta.lines must be an Array"),(!t.start||!t.end)&&r(t,"delta.start/end must be an present");var n=t.start;i(e,t.start)||r(t,"delta.start must be contained in document");var s=t.end;t.action=="remove"&&!i(e,s)&&r(t,"delta.end must contained in document for 'remove' actions");var o=s.row-n.row,u=s.column-(o==0?n.column:0);(o!=t.lines.length-1||t.lines[o].length!=u)&&r(t,"delta.range must match delta lines")}t.applyDelta=function(e,t,n){var r=t.start.row,i=t.start.column,s=e[r]||"";switch(t.action){case"insert":var o=t.lines;if(o.length===1)e[r]=s.substring(0,i)+t.lines[0]+s.substring(i);else{var u=[r,1].concat(t.lines);e.splice.apply(e,u),e[r]=s.substring(0,i)+e[r],e[r+t.lines.length-1]+=s.substring(i)}break;case"remove":var a=t.end.column,f=t.end.row;r===f?e[r]=s.substring(0,i)+s.substring(a):e.splice(r,f-r+1,s.substring(0,i)+e[f].substring(a))}}}),define("ace/anchor",["require","exports","module","ace/lib/oop","ace/lib/event_emitter"],function(e,t,n){"use strict";function o(e,t,n){var r=n?e.column<=t.column:e.columnthis.row)return;var t=u(e,{row:this.row,column:this.column},this.$insertRight);this.setPosition(t.row,t.column,!0)},e.prototype.setPosition=function(e,t,n){var r;n?r={row:e,column:t}:r=this.$clipPositionToDocument(e,t);if(this.row==r.row&&this.column==r.column)return;var i={row:this.row,column:this.column};this.row=r.row,this.column=r.column,this._signal("change",{old:i,value:r})},e.prototype.detach=function(){this.document.off("change",this.$onChange)},e.prototype.attach=function(e){this.document=e||this.document,this.document.on("change",this.$onChange)},e.prototype.$clipPositionToDocument=function(e,t){var n={};return e>=this.document.getLength()?(n.row=Math.max(0,this.document.getLength()-1),n.column=this.document.getLine(n.row).length):e<0?(n.row=0,n.column=0):(n.row=e,n.column=Math.min(this.document.getLine(n.row).length,Math.max(0,t))),t<0&&(n.column=0),n},e}();s.prototype.$insertRight=!1,r.implement(s.prototype,i),t.Anchor=s}),define("ace/document",["require","exports","module","ace/lib/oop","ace/apply_delta","ace/lib/event_emitter","ace/range","ace/anchor"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./apply_delta").applyDelta,s=e("./lib/event_emitter").EventEmitter,o=e("./range").Range,u=e("./anchor").Anchor,a=function(){function e(e){this.$lines=[""],e.length===0?this.$lines=[""]:Array.isArray(e)?this.insertMergedLines({row:0,column:0},e):this.insert({row:0,column:0},e)}return e.prototype.setValue=function(e){var t=this.getLength()-1;this.remove(new o(0,0,t,this.getLine(t).length)),this.insert({row:0,column:0},e||"")},e.prototype.getValue=function(){return this.getAllLines().join(this.getNewLineCharacter())},e.prototype.createAnchor=function(e,t){return new u(this,e,t)},e.prototype.$detectNewLine=function(e){var t=e.match(/^.*?(\r\n|\r|\n)/m);this.$autoNewLine=t?t[1]:"\n",this._signal("changeNewLineMode")},e.prototype.getNewLineCharacter=function(){switch(this.$newLineMode){case"windows":return"\r\n";case"unix":return"\n";default:return this.$autoNewLine||"\n"}},e.prototype.setNewLineMode=function(e){if(this.$newLineMode===e)return;this.$newLineMode=e,this._signal("changeNewLineMode")},e.prototype.getNewLineMode=function(){return this.$newLineMode},e.prototype.isNewLine=function(e){return e=="\r\n"||e=="\r"||e=="\n"},e.prototype.getLine=function(e){return this.$lines[e]||""},e.prototype.getLines=function(e,t){return this.$lines.slice(e,t+1)},e.prototype.getAllLines=function(){return this.getLines(0,this.getLength())},e.prototype.getLength=function(){return this.$lines.length},e.prototype.getTextRange=function(e){return this.getLinesForRange(e).join(this.getNewLineCharacter())},e.prototype.getLinesForRange=function(e){var t;if(e.start.row===e.end.row)t=[this.getLine(e.start.row).substring(e.start.column,e.end.column)];else{t=this.getLines(e.start.row,e.end.row),t[0]=(t[0]||"").substring(e.start.column);var n=t.length-1;e.end.row-e.start.row==n&&(t[n]=t[n].substring(0,e.end.column))}return t},e.prototype.insertLines=function(e,t){return console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."),this.insertFullLines(e,t)},e.prototype.removeLines=function(e,t){return console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."),this.removeFullLines(e,t)},e.prototype.insertNewLine=function(e){return console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead."),this.insertMergedLines(e,["",""])},e.prototype.insert=function(e,t){return this.getLength()<=1&&this.$detectNewLine(t),this.insertMergedLines(e,this.$split(t))},e.prototype.insertInLine=function(e,t){var n=this.clippedPos(e.row,e.column),r=this.pos(e.row,e.column+t.length);return this.applyDelta({start:n,end:r,action:"insert",lines:[t]},!0),this.clonePos(r)},e.prototype.clippedPos=function(e,t){var n=this.getLength();e===undefined?e=n:e<0?e=0:e>=n&&(e=n-1,t=undefined);var r=this.getLine(e);return t==undefined&&(t=r.length),t=Math.min(Math.max(t,0),r.length),{row:e,column:t}},e.prototype.clonePos=function(e){return{row:e.row,column:e.column}},e.prototype.pos=function(e,t){return{row:e,column:t}},e.prototype.$clipPosition=function(e){var t=this.getLength();return e.row>=t?(e.row=Math.max(0,t-1),e.column=this.getLine(t-1).length):(e.row=Math.max(0,e.row),e.column=Math.min(Math.max(e.column,0),this.getLine(e.row).length)),e},e.prototype.insertFullLines=function(e,t){e=Math.min(Math.max(e,0),this.getLength());var n=0;e0,r=t=0&&this.applyDelta({start:this.pos(e,this.getLine(e).length),end:this.pos(e+1,0),action:"remove",lines:["",""]})},e.prototype.replace=function(e,t){e instanceof o||(e=o.fromPoints(e.start,e.end));if(t.length===0&&e.isEmpty())return e.start;if(t==this.getTextRange(e))return e.end;this.remove(e);var n;return t?n=this.insert(e.start,t):n=e.start,n},e.prototype.applyDeltas=function(e){for(var t=0;t=0;t--)this.revertDelta(e[t])},e.prototype.applyDelta=function(e,t){var n=e.action=="insert";if(n?e.lines.length<=1&&!e.lines[0]:!o.comparePoints(e.start,e.end))return;n&&e.lines.length>2e4?this.$splitAndapplyLargeDelta(e,2e4):(i(this.$lines,e,t),this._signal("change",e))},e.prototype.$safeApplyDelta=function(e){var t=this.$lines.length;(e.action=="remove"&&e.start.row20){n.running=setTimeout(n.$worker,20);break}}n.currentLine=t,r==-1&&(r=t),s<=r&&n.fireUpdateEvent(s,r)}}return e.prototype.setTokenizer=function(e){this.tokenizer=e,this.lines=[],this.states=[],this.start(0)},e.prototype.setDocument=function(e){this.doc=e,this.lines=[],this.states=[],this.stop()},e.prototype.fireUpdateEvent=function(e,t){var n={first:e,last:t};this._signal("update",{data:n})},e.prototype.start=function(e){this.currentLine=Math.min(e||0,this.currentLine,this.doc.getLength()),this.lines.splice(this.currentLine,this.lines.length),this.states.splice(this.currentLine,this.states.length),this.stop(),this.running=setTimeout(this.$worker,700)},e.prototype.scheduleStart=function(){this.running||(this.running=setTimeout(this.$worker,700))},e.prototype.$updateOnChange=function(e){var t=e.start.row,n=e.end.row-t;if(n===0)this.lines[t]=null;else if(e.action=="remove")this.lines.splice(t,n+1,null),this.states.splice(t,n+1,null);else{var r=Array(n+1);r.unshift(t,1),this.lines.splice.apply(this.lines,r),this.states.splice.apply(this.states,r)}this.currentLine=Math.min(t,this.currentLine,this.doc.getLength()),this.stop()},e.prototype.stop=function(){this.running&&clearTimeout(this.running),this.running=!1},e.prototype.getTokens=function(e){return this.lines[e]||this.$tokenizeRow(e)},e.prototype.getState=function(e){return this.currentLine==e&&this.$tokenizeRow(e),this.states[e]||"start"},e.prototype.$tokenizeRow=function(e){var t=this.doc.getLine(e),n=this.states[e-1],r=this.tokenizer.getLineTokens(t,n,e);return this.states[e]+""!=r.state+""?(this.states[e]=r.state,this.lines[e+1]=null,this.currentLine>e+1&&(this.currentLine=e+1)):this.currentLine==e&&(this.currentLine=e+1),this.lines[e]=r.tokens},e.prototype.cleanup=function(){this.running=!1,this.lines=[],this.states=[],this.currentLine=0,this.removeAllListeners()},e}();r.implement(s.prototype,i),t.BackgroundTokenizer=s}),define("ace/search_highlight",["require","exports","module","ace/lib/lang","ace/range"],function(e,t,n){"use strict";var r=e("./lib/lang"),i=e("./range").Range,s=function(){function e(e,t,n){n===void 0&&(n="text"),this.setRegexp(e),this.clazz=t,this.type=n}return e.prototype.setRegexp=function(e){if(this.regExp+""==e+"")return;this.regExp=e,this.cache=[]},e.prototype.update=function(e,t,n,s){if(!this.regExp)return;var o=s.firstRow,u=s.lastRow,a={};for(var f=o;f<=u;f++){var l=this.cache[f];l==null&&(l=r.getMatchOffsets(n.getLine(f),this.regExp),l.length>this.MAX_RANGES&&(l=l.slice(0,this.MAX_RANGES)),l=l.map(function(e){return new i(f,e.offset,f,e.offset+e.length)}),this.cache[f]=l.length?l:"");for(var c=l.length;c--;){var h=l[c].toScreenRange(n),p=h.toString();if(a[p])continue;a[p]=!0,t.drawSingleLineMarker(e,h,this.clazz,s)}}},e}();s.prototype.MAX_RANGES=500,t.SearchHighlight=s}),define("ace/undomanager",["require","exports","module","ace/range"],function(e,t,n){"use strict";function i(e,t){for(var n=t;n--;){var r=e[n];if(r&&!r[0].ignore){while(n0){a.row+=i,a.column+=a.row==r.row?s:0;continue}!t&&l<=0&&(a.row=n.row,a.column=n.column,l===0&&(a.bias=1))}}function f(e){return{row:e.row,column:e.column}}function l(e){return{start:f(e.start),end:f(e.end),action:e.action,lines:e.lines.slice()}}function c(e){e=e||this;if(Array.isArray(e))return e.map(c).join("\n");var t="";e.action?(t=e.action=="insert"?"+":"-",t+="["+e.lines+"]"):e.value&&(Array.isArray(e.value)?t=e.value.map(h).join("\n"):t=h(e.value)),e.start&&(t+=h(e));if(e.id||e.rev)t+=" ("+(e.id||e.rev)+")";return t}function h(e){return e.start.row+":"+e.start.column+"=>"+e.end.row+":"+e.end.column}function p(e,t){var n=e.action=="insert",r=t.action=="insert";if(n&&r)if(o(t.start,e.end)>=0)m(t,e,-1);else{if(!(o(t.start,e.start)<=0))return null;m(e,t,1)}else if(n&&!r)if(o(t.start,e.end)>=0)m(t,e,-1);else{if(!(o(t.end,e.start)<=0))return null;m(e,t,-1)}else if(!n&&r)if(o(t.start,e.start)>=0)m(t,e,1);else{if(!(o(t.start,e.start)<=0))return null;m(e,t,1)}else if(!n&&!r)if(o(t.start,e.start)>=0)m(t,e,1);else{if(!(o(t.end,e.start)<=0))return null;m(e,t,-1)}return[t,e]}function d(e,t){for(var n=e.length;n--;)for(var r=0;r=0?m(e,t,-1):o(e.start,t.start)<=0?m(t,e,1):(m(e,s.fromPoints(t.start,e.start),-1),m(t,e,1));else if(!n&&r)o(t.start,e.end)>=0?m(t,e,-1):o(t.start,e.start)<=0?m(e,t,1):(m(t,s.fromPoints(e.start,t.start),-1),m(e,t,1));else if(!n&&!r)if(o(t.start,e.end)>=0)m(t,e,-1);else{if(!(o(t.end,e.start)<=0)){var i,u;return o(e.start,t.start)<0&&(i=e,e=y(e,t.start)),o(e.end,t.end)>0&&(u=y(e,t.end)),g(t.end,e.start,e.end,-1),u&&!i&&(e.lines=u.lines,e.start=u.start,e.end=u.end,u=e),[t,i,u].filter(Boolean)}m(e,t,-1)}return[t,e]}function m(e,t,n){g(e.start,t.start,t.end,n),g(e.end,t.start,t.end,n)}function g(e,t,n,r){e.row==(r==1?t:n).row&&(e.column+=r*(n.column-t.column)),e.row+=r*(n.row-t.row)}function y(e,t){var n=e.lines,r=e.end;e.end=f(t);var i=e.end.row-e.start.row,s=n.splice(i,n.length),o=i?t.column:t.column-e.start.column;n.push(s[0].substring(0,o)),s[0]=s[0].substr(o);var u={start:f(t),end:r,lines:s,action:e.action};return u}function b(e,t){t=l(t);for(var n=e.length;n--;){var r=e[n];for(var i=0;ithis.$undoDepth-1&&this.$undoStack.splice(0,r-this.$undoDepth+1),this.$undoStack.push(this.lastDeltas),e.id=this.$rev=++this.$maxRev}if(e.action=="remove"||e.action=="insert")this.$lastDelta=e;this.lastDeltas.push(e)},e.prototype.addSelection=function(e,t){this.selections.push({value:e,rev:t||this.$rev})},e.prototype.startNewGroup=function(){return this.lastDeltas=null,this.$rev},e.prototype.markIgnored=function(e,t){t==null&&(t=this.$rev+1);var n=this.$undoStack;for(var r=n.length;r--;){var i=n[r][0];if(i.id<=e)break;i.id0},e.prototype.canRedo=function(){return this.$redoStack.length>0},e.prototype.bookmark=function(e){e==undefined&&(e=this.$rev),this.mark=e},e.prototype.isAtBookmark=function(){return this.$rev===this.mark},e.prototype.toJSON=function(){return{$redoStack:this.$redoStack,$undoStack:this.$undoStack}},e.prototype.fromJSON=function(e){this.reset(),this.$undoStack=e.$undoStack,this.$redoStack=e.$redoStack},e.prototype.$prettyPrint=function(e){return e?c(e):c(this.$undoStack)+"\n---\n"+c(this.$redoStack)},e}();r.prototype.hasUndo=r.prototype.canUndo,r.prototype.hasRedo=r.prototype.canRedo,r.prototype.isClean=r.prototype.isAtBookmark,r.prototype.markClean=r.prototype.bookmark;var s=e("./range").Range,o=s.comparePoints,u=s.comparePoints;t.UndoManager=r}),define("ace/edit_session/fold_line",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){function e(e,t){this.foldData=e,Array.isArray(t)?this.folds=t:t=this.folds=[t];var n=t[t.length-1];this.range=new r(t[0].start.row,t[0].start.column,n.end.row,n.end.column),this.start=this.range.start,this.end=this.range.end,this.folds.forEach(function(e){e.setFoldLine(this)},this)}return e.prototype.shiftRow=function(e){this.start.row+=e,this.end.row+=e,this.folds.forEach(function(t){t.start.row+=e,t.end.row+=e})},e.prototype.addFold=function(e){if(e.sameRow){if(e.start.rowthis.endRow)throw new Error("Can't add a fold to this FoldLine as it has no connection");this.folds.push(e),this.folds.sort(function(e,t){return-e.range.compareEnd(t.start.row,t.start.column)}),this.range.compareEnd(e.start.row,e.start.column)>0?(this.end.row=e.end.row,this.end.column=e.end.column):this.range.compareStart(e.end.row,e.end.column)<0&&(this.start.row=e.start.row,this.start.column=e.start.column)}else if(e.start.row==this.end.row)this.folds.push(e),this.end.row=e.end.row,this.end.column=e.end.column;else{if(e.end.row!=this.start.row)throw new Error("Trying to add fold to FoldRow that doesn't have a matching row");this.folds.unshift(e),this.start.row=e.start.row,this.start.column=e.start.column}e.foldLine=this},e.prototype.containsRow=function(e){return e>=this.start.row&&e<=this.end.row},e.prototype.walk=function(e,t,n){var r=0,i=this.folds,s,o,u,a=!0;t==null&&(t=this.end.row,n=this.end.column);for(var f=0;f0)continue;var a=i(e,o.start);return u===0?t&&a!==0?-s-2:s:a>0||a===0&&!t?s:-s-1}return-s-1},e.prototype.add=function(e){var t=!e.isEmpty(),n=this.pointIndex(e.start,t);n<0&&(n=-n-1);var r=this.pointIndex(e.end,t,n);return r<0?r=-r-1:r++,this.ranges.splice(n,r-n,e)},e.prototype.addList=function(e){var t=[];for(var n=e.length;n--;)t.push.apply(t,this.add(e[n]));return t},e.prototype.substractPoint=function(e){var t=this.pointIndex(e);if(t>=0)return this.ranges.splice(t,1)},e.prototype.merge=function(){var e=[],t=this.ranges;t=t.sort(function(e,t){return i(e.start,t.start)});var n=t[0],r;for(var s=1;s=0},e.prototype.containsPoint=function(e){return this.pointIndex(e)>=0},e.prototype.rangeAtPoint=function(e){var t=this.pointIndex(e);if(t>=0)return this.ranges[t]},e.prototype.clipRows=function(e,t){var n=this.ranges;if(n[0].start.row>t||n[n.length-1].start.row=r)break}if(e.action=="insert"){var f=i-r,l=-t.column+n.column;for(;or)break;a.start.row==r&&a.start.column>=t.column&&(a.start.column==t.column&&this.$bias<=0||(a.start.column+=l,a.start.row+=f));if(a.end.row==r&&a.end.column>=t.column){if(a.end.column==t.column&&this.$bias<0)continue;a.end.column==t.column&&l>0&&oa.start.column&&a.end.column==s[o+1].start.column&&(a.end.column-=l),a.end.column+=l,a.end.row+=f}}}else{var f=r-i,l=t.column-n.column;for(;oi)break;if(a.end.rowt.column)a.end.column=t.column,a.end.row=t.row}else a.end.column+=l,a.end.row+=f;else a.end.row>i&&(a.end.row+=f);if(a.start.rowt.column)a.start.column=t.column,a.start.row=t.row}else a.start.column+=l,a.start.row+=f;else a.start.row>i&&(a.start.row+=f)}}if(f!=0&&o=e)return i;if(i.end.row>e)return null}return null},this.getNextFoldLine=function(e,t){var n=this.$foldData,r=0;t&&(r=n.indexOf(t)),r==-1&&(r=0);for(r;r=e)return i}return null},this.getFoldedRowCount=function(e,t){var n=this.$foldData,r=t-e+1;for(var i=0;i=t){u=e?r-=t-u:r=0);break}o>=e&&(u>=e?r-=o-u:r-=o-e+1)}return r},this.$addFoldLine=function(e){return this.$foldData.push(e),this.$foldData.sort(function(e,t){return e.start.row-t.start.row}),e},this.addFold=function(e,t){var n=this.$foldData,r=!1,o;e instanceof s?o=e:(o=new s(t,e),o.collapseChildren=t.collapseChildren),this.$clipRangeToDocument(o.range);var u=o.start.row,a=o.start.column,f=o.end.row,l=o.end.column,c=this.getFoldAt(u,a,1),h=this.getFoldAt(f,l,-1);if(c&&h==c)return c.addSubFold(o);c&&!c.range.isStart(u,a)&&this.removeFold(c),h&&!h.range.isEnd(f,l)&&this.removeFold(h);var p=this.getFoldsInRange(o.range);p.length>0&&(this.removeFolds(p),o.collapseChildren||p.forEach(function(e){o.addSubFold(e)}));for(var d=0;d0&&this.foldAll(e.start.row+1,e.end.row,e.collapseChildren-1),e.subFolds=[]},this.expandFolds=function(e){e.forEach(function(e){this.expandFold(e)},this)},this.unfold=function(e,t){var n,i;if(e==null)n=new r(0,0,this.getLength(),0),t==null&&(t=!0);else if(typeof e=="number")n=new r(e,0,e,this.getLine(e).length);else if("row"in e)n=r.fromPoints(e,e);else{if(Array.isArray(e))return i=[],e.forEach(function(e){i=i.concat(this.unfold(e))},this),i;n=e}i=this.getFoldsInRangeList(n);var s=i;while(i.length==1&&r.comparePoints(i[0].start,n.start)<0&&r.comparePoints(i[0].end,n.end)>0)this.expandFolds(i),i=this.getFoldsInRangeList(n);t!=0?this.removeFolds(i):this.expandFolds(i);if(s.length)return s},this.isRowFolded=function(e,t){return!!this.getFoldLine(e,t)},this.getRowFoldEnd=function(e,t){var n=this.getFoldLine(e,t);return n?n.end.row:e},this.getRowFoldStart=function(e,t){var n=this.getFoldLine(e,t);return n?n.start.row:e},this.getFoldDisplayLine=function(e,t,n,r,i){r==null&&(r=e.start.row),i==null&&(i=0),t==null&&(t=e.end.row),n==null&&(n=this.getLine(t).length);var s=this.doc,o="";return e.walk(function(e,t,n,u){if(tl)break}while(s&&a.test(s.type)&&!/^comment.start/.test(s.type));s=i.stepBackward()}else s=i.getCurrentToken();return f.end.row=i.getCurrentTokenRow(),f.end.column=i.getCurrentTokenColumn(),/^comment.end/.test(s.type)||(f.end.column+=s.value.length-2),f}},this.foldAll=function(e,t,n,r){n==undefined&&(n=1e5);var i=this.foldWidgets;if(!i)return;t=t||this.getLength(),e=e||0;for(var s=e;s=e&&(s=o.end.row,o.collapseChildren=n,this.addFold("...",o))}},this.foldToLevel=function(e){this.foldAll();while(e-->0)this.unfold(null,!1)},this.foldAllComments=function(){var e=this;this.foldAll(null,null,null,function(t){var n=e.getTokens(t);for(var r=0;r=0){var s=n[r];s==null&&(s=n[r]=this.getFoldWidget(r));if(s=="start"){var o=this.getFoldWidgetRange(r);i||(i=o);if(o&&o.end.row>=e)break}r--}return{range:r!==-1&&o,firstRange:i}},this.onFoldWidgetClick=function(e,t){t instanceof u&&(t=t.domEvent);var n={children:t.shiftKey,all:t.ctrlKey||t.metaKey,siblings:t.altKey},r=this.$toggleFoldWidget(e,n);if(!r){var i=t.target||t.srcElement;i&&/ace_fold-widget/.test(i.className)&&(i.className+=" ace_invalid")}},this.$toggleFoldWidget=function(e,t){if(!this.getFoldWidget)return;var n=this.getFoldWidget(e),r=this.getLine(e),i=n==="end"?-1:1,s=this.getFoldAt(e,i===-1?0:r.length,i);if(s)return t.children||t.all?this.removeFold(s):this.expandFold(s),s;var o=this.getFoldWidgetRange(e,!0);if(o&&!o.isMultiLine()){s=this.getFoldAt(o.start.row,o.start.column,1);if(s&&o.isEqual(s.range))return this.removeFold(s),s}if(t.siblings){var u=this.getParentFoldRangeData(e);if(u.range)var a=u.range.start.row+1,f=u.range.end.row;this.foldAll(a,f,t.all?1e4:0)}else t.children?(f=o?o.end.row:this.getLength(),this.foldAll(e+1,f,t.all?1e4:0)):o&&(t.all&&(o.collapseChildren=1e4),this.addFold("...",o));return o},this.toggleFoldWidget=function(e){var t=this.selection.getCursor().row;t=this.getRowFoldStart(t);var n=this.$toggleFoldWidget(t,{});if(n)return;var r=this.getParentFoldRangeData(t,!0);n=r.range||r.firstRange;if(n){t=n.start.row;var i=this.getFoldAt(t,this.getLine(t).length,1);i?this.removeFold(i):this.addFold("...",n)}},this.updateFoldWidgets=function(e){var t=e.start.row,n=e.end.row-t;if(n===0)this.foldWidgets[t]=null;else if(e.action=="remove")this.foldWidgets.splice(t,n+1,null);else{var r=Array(n+1);r.unshift(t,1),this.foldWidgets.splice.apply(this.foldWidgets,r)}},this.tokenizerUpdateFoldWidgets=function(e){var t=e.data;t.first!=t.last&&this.foldWidgets.length>t.first&&this.foldWidgets.splice(t.first,this.foldWidgets.length)}}var r=e("../range").Range,i=e("./fold_line").FoldLine,s=e("./fold").Fold,o=e("../token_iterator").TokenIterator,u=e("../mouse/mouse_event").MouseEvent;t.Folding=a}),define("ace/edit_session/bracket_match",["require","exports","module","ace/token_iterator","ace/range"],function(e,t,n){"use strict";function s(){this.findMatchingBracket=function(e,t){if(e.column==0)return null;var n=t||this.getLine(e.row).charAt(e.column-1);if(n=="")return null;var r=n.match(/([\(\[\{])|([\)\]\}])/);return r?r[1]?this.$findClosingBracket(r[1],e):this.$findOpeningBracket(r[2],e):null},this.getBracketRange=function(e){var t=this.getLine(e.row),n=!0,r,s=t.charAt(e.column-1),o=s&&s.match(/([\(\[\{])|([\)\]\}])/);o||(s=t.charAt(e.column),e={row:e.row,column:e.column+1},o=s&&s.match(/([\(\[\{])|([\)\]\}])/),n=!1);if(!o)return null;if(o[1]){var u=this.$findClosingBracket(o[1],e);if(!u)return null;r=i.fromPoints(e,u),n||(r.end.column++,r.start.column--),r.cursor=r.end}else{var u=this.$findOpeningBracket(o[2],e);if(!u)return null;r=i.fromPoints(u,e),n||(r.start.column++,r.end.column--),r.cursor=r.start}return r},this.getMatchingBracketRanges=function(e,t){var n=this.getLine(e.row),r=/([\(\[\{])|([\)\]\}])/,s=!t&&n.charAt(e.column-1),o=s&&s.match(r);o||(s=(t===undefined||t)&&n.charAt(e.column),e={row:e.row,column:e.column+1},o=s&&s.match(r));if(!o)return null;var u=new i(e.row,e.column-1,e.row,e.column),a=o[1]?this.$findClosingBracket(o[1],e):this.$findOpeningBracket(o[2],e);if(!a)return[u];var f=new i(a.row,a.column,a.row,a.column+1);return[u,f]},this.$brackets={")":"(","(":")","]":"[","[":"]","{":"}","}":"{","<":">",">":"<"},this.$findOpeningBracket=function(e,t,n){var i=this.$brackets[e],s=1,o=new r(this,t.row,t.column),u=o.getCurrentToken();u||(u=o.stepForward());if(!u)return;n||(n=new RegExp("(\\.?"+u.type.replace(".","\\.").replace("rparen",".paren").replace(/\b(?:end)\b/,"(?:start|begin|end)").replace(/-close\b/,"-(close|open)")+")+"));var a=t.column-o.getCurrentTokenColumn()-2,f=u.value;for(;;){while(a>=0){var l=f.charAt(a);if(l==i){s-=1;if(s==0)return{row:o.getCurrentTokenRow(),column:a+o.getCurrentTokenColumn()}}else l==e&&(s+=1);a-=1}do u=o.stepBackward();while(u&&!n.test(u.type));if(u==null)break;f=u.value,a=f.length-1}return null},this.$findClosingBracket=function(e,t,n){var i=this.$brackets[e],s=1,o=new r(this,t.row,t.column),u=o.getCurrentToken();u||(u=o.stepForward());if(!u)return;n||(n=new RegExp("(\\.?"+u.type.replace(".","\\.").replace("lparen",".paren").replace(/\b(?:start|begin)\b/,"(?:start|begin|end)").replace(/-open\b/,"-(close|open)")+")+"));var a=t.column-o.getCurrentTokenColumn();for(;;){var f=u.value,l=f.length;while(a"?r=!0:t.type.indexOf("tag-name")!==-1&&(n=!0));while(t&&!n);return t},this.$findClosingTag=function(e,t){var n,r=t.value,s=t.value,o=0,u=new i(e.getCurrentTokenRow(),e.getCurrentTokenColumn(),e.getCurrentTokenRow(),e.getCurrentTokenColumn()+1);t=e.stepForward();var a=new i(e.getCurrentTokenRow(),e.getCurrentTokenColumn(),e.getCurrentTokenRow(),e.getCurrentTokenColumn()+t.value.length),f=!1;do{n=t,t=e.stepForward();if(t){if(t.value===">"&&!f){var l=new i(e.getCurrentTokenRow(),e.getCurrentTokenColumn(),e.getCurrentTokenRow(),e.getCurrentTokenColumn()+1);f=!0}if(t.type.indexOf("tag-name")!==-1){r=t.value;if(s===r)if(n.value==="<")o++;else if(n.value==="")return;var p=new i(e.getCurrentTokenRow(),e.getCurrentTokenColumn(),e.getCurrentTokenRow(),e.getCurrentTokenColumn()+1)}}}else if(s===r&&t.value==="/>"){o--;if(o<0)var c=new i(e.getCurrentTokenRow(),e.getCurrentTokenColumn(),e.getCurrentTokenRow(),e.getCurrentTokenColumn()+2),h=c,p=h,l=new i(a.end.row,a.end.column,a.end.row,a.end.column+1)}}}while(t&&o>=0);if(u&&l&&c&&p&&a&&h)return{openTag:new i(u.start.row,u.start.column,l.end.row,l.end.column),closeTag:new i(c.start.row,c.start.column,p.end.row,p.end.column),openTagName:a,closeTagName:h}},this.$findOpeningTag=function(e,t){var n=e.getCurrentToken(),r=t.value,s=0,o=e.getCurrentTokenRow(),u=e.getCurrentTokenColumn(),a=u+2,f=new i(o,u,o,a);e.stepForward();var l=new i(e.getCurrentTokenRow(),e.getCurrentTokenColumn(),e.getCurrentTokenRow(),e.getCurrentTokenColumn()+t.value.length);t=e.stepForward();if(!t||t.value!==">")return;var c=new i(e.getCurrentTokenRow(),e.getCurrentTokenColumn(),e.getCurrentTokenRow(),e.getCurrentTokenColumn()+1);e.stepBackward(),e.stepBackward();do{t=n,o=e.getCurrentTokenRow(),u=e.getCurrentTokenColumn(),a=u+t.value.length,n=e.stepBackward();if(t)if(t.type.indexOf("tag-name")!==-1){if(r===t.value)if(n.value==="<"){s++;if(s>0){var h=new i(o,u,o,a),p=new i(e.getCurrentTokenRow(),e.getCurrentTokenColumn(),e.getCurrentTokenRow(),e.getCurrentTokenColumn()+1);do t=e.stepForward();while(t&&t.value!==">");var d=new i(e.getCurrentTokenRow(),e.getCurrentTokenColumn(),e.getCurrentTokenRow(),e.getCurrentTokenColumn()+1)}}else n.value===""){var v=0,m=n;while(m){if(m.type.indexOf("tag-name")!==-1&&m.value===r){s--;break}if(m.value==="<")break;m=e.stepBackward(),v++}for(var g=0;g=4352&&e<=4447||e>=4515&&e<=4519||e>=4602&&e<=4607||e>=9001&&e<=9002||e>=11904&&e<=11929||e>=11931&&e<=12019||e>=12032&&e<=12245||e>=12272&&e<=12283||e>=12288&&e<=12350||e>=12353&&e<=12438||e>=12441&&e<=12543||e>=12549&&e<=12589||e>=12593&&e<=12686||e>=12688&&e<=12730||e>=12736&&e<=12771||e>=12784&&e<=12830||e>=12832&&e<=12871||e>=12880&&e<=13054||e>=13056&&e<=19903||e>=19968&&e<=42124||e>=42128&&e<=42182||e>=43360&&e<=43388||e>=44032&&e<=55203||e>=55216&&e<=55238||e>=55243&&e<=55291||e>=63744&&e<=64255||e>=65040&&e<=65049||e>=65072&&e<=65106||e>=65108&&e<=65126||e>=65128&&e<=65131||e>=65281&&e<=65376||e>=65504&&e<=65510}var r=e("./lib/oop"),i=e("./lib/lang"),s=e("./bidihandler").BidiHandler,o=e("./config"),u=e("./lib/event_emitter").EventEmitter,a=e("./selection").Selection,f=e("./mode/text").Mode,l=e("./range").Range,c=e("./document").Document,h=e("./background_tokenizer").BackgroundTokenizer,p=e("./search_highlight").SearchHighlight,d=e("./undomanager").UndoManager,v=function(){function e(t,n){this.doc,this.$breakpoints=[],this.$decorations=[],this.$frontMarkers={},this.$backMarkers={},this.$markerId=1,this.$undoSelect=!0,this.$foldData=[],this.id="session"+ ++e.$uid,this.$foldData.toString=function(){return this.join("\n")},this.bgTokenizer=new h((new f).getTokenizer(),this);var r=this;this.bgTokenizer.on("update",function(e){r._signal("tokenizerUpdate",e)}),this.on("changeFold",this.onChangeFold.bind(this)),this.$onChange=this.onChange.bind(this);if(typeof t!="object"||!t.getLine)t=new c(t);this.setDocument(t),this.selection=new a(this),this.$bidiHandler=new s(this),o.resetOptions(this),this.setMode(n),o._signal("session",this),this.destroyed=!1}return e.prototype.setDocument=function(e){this.doc&&this.doc.off("change",this.$onChange),this.doc=e,e.on("change",this.$onChange,!0),this.bgTokenizer.setDocument(this.getDocument()),this.resetCaches()},e.prototype.getDocument=function(){return this.doc},e.prototype.$resetRowCache=function(e){if(!e){this.$docRowCache=[],this.$screenRowCache=[];return}var t=this.$docRowCache.length,n=this.$getRowCacheIndex(this.$docRowCache,e)+1;t>n&&(this.$docRowCache.splice(n,t),this.$screenRowCache.splice(n,t))},e.prototype.$getRowCacheIndex=function(e,t){var n=0,r=e.length-1;while(n<=r){var i=n+r>>1,s=e[i];if(t>s)n=i+1;else{if(!(t=t)break}return r=n[s],r?(r.index=s,r.start=i-r.value.length,r):null},e.prototype.setUndoManager=function(e){this.$undoManager=e,this.$informUndoManager&&this.$informUndoManager.cancel();if(e){var t=this;e.addSession(this),this.$syncInformUndoManager=function(){t.$informUndoManager.cancel(),t.mergeUndoDeltas=!1},this.$informUndoManager=i.delayedCall(this.$syncInformUndoManager)}else this.$syncInformUndoManager=function(){}},e.prototype.markUndoGroup=function(){this.$syncInformUndoManager&&this.$syncInformUndoManager()},e.prototype.getUndoManager=function(){return this.$undoManager||this.$defaultUndoManager},e.prototype.getTabString=function(){return this.getUseSoftTabs()?i.stringRepeat(" ",this.getTabSize()):" "},e.prototype.setUseSoftTabs=function(e){this.setOption("useSoftTabs",e)},e.prototype.getUseSoftTabs=function(){return this.$useSoftTabs&&!this.$mode.$indentWithTabs},e.prototype.setTabSize=function(e){this.setOption("tabSize",e)},e.prototype.getTabSize=function(){return this.$tabSize},e.prototype.isTabStop=function(e){return this.$useSoftTabs&&e.column%this.$tabSize===0},e.prototype.setNavigateWithinSoftTabs=function(e){this.setOption("navigateWithinSoftTabs",e)},e.prototype.getNavigateWithinSoftTabs=function(){return this.$navigateWithinSoftTabs},e.prototype.setOverwrite=function(e){this.setOption("overwrite",e)},e.prototype.getOverwrite=function(){return this.$overwrite},e.prototype.toggleOverwrite=function(){this.setOverwrite(!this.$overwrite)},e.prototype.addGutterDecoration=function(e,t){this.$decorations[e]||(this.$decorations[e]=""),this.$decorations[e]+=" "+t,this._signal("changeBreakpoint",{})},e.prototype.removeGutterDecoration=function(e,t){this.$decorations[e]=(this.$decorations[e]||"").replace(" "+t,""),this._signal("changeBreakpoint",{})},e.prototype.getBreakpoints=function(){return this.$breakpoints},e.prototype.setBreakpoints=function(e){this.$breakpoints=[];for(var t=0;t0&&(r=!!n.charAt(t-1).match(this.tokenRe)),r||(r=!!n.charAt(t).match(this.tokenRe));if(r)var i=this.tokenRe;else if(/^\s+$/.test(n.slice(t-1,t+1)))var i=/\s/;else var i=this.nonTokenRe;var s=t;if(s>0){do s--;while(s>=0&&n.charAt(s).match(i));s++}var o=t;while(oe&&(e=t.screenWidth)}),this.lineWidgetWidth=e},e.prototype.$computeWidth=function(e){if(this.$modified||e){this.$modified=!1;if(this.$useWrapMode)return this.screenWidth=this.$wrapLimit;var t=this.doc.getAllLines(),n=this.$rowLengthCache,r=0,i=0,s=this.$foldData[i],o=s?s.start.row:Infinity,u=t.length;for(var a=0;ao){a=s.end.row+1;if(a>=u)break;s=this.$foldData[i++],o=s?s.start.row:Infinity}n[a]==null&&(n[a]=this.$getStringScreenWidth(t[a])[0]),n[a]>r&&(r=n[a])}this.screenWidth=r}},e.prototype.getLine=function(e){return this.doc.getLine(e)},e.prototype.getLines=function(e,t){return this.doc.getLines(e,t)},e.prototype.getLength=function(){return this.doc.getLength()},e.prototype.getTextRange=function(e){return this.doc.getTextRange(e||this.selection.getRange())},e.prototype.insert=function(e,t){return this.doc.insert(e,t)},e.prototype.remove=function(e){return this.doc.remove(e)},e.prototype.removeFullLines=function(e,t){return this.doc.removeFullLines(e,t)},e.prototype.undoChanges=function(e,t){if(!e.length)return;this.$fromUndo=!0;for(var n=e.length-1;n!=-1;n--){var r=e[n];r.action=="insert"||r.action=="remove"?this.doc.revertDelta(r):r.folds&&this.addFolds(r.folds)}!t&&this.$undoSelect&&(e.selectionBefore?this.selection.fromJSON(e.selectionBefore):this.selection.setRange(this.$getUndoSelection(e,!0))),this.$fromUndo=!1},e.prototype.redoChanges=function(e,t){if(!e.length)return;this.$fromUndo=!0;for(var n=0;ne.end.column&&(s.start.column+=u),s.end.row==e.end.row&&s.end.column>e.end.column&&(s.end.column+=u)),o&&s.start.row>=e.end.row&&(s.start.row+=o,s.end.row+=o)}s.end=this.insert(s.start,r);if(i.length){var a=e.start,f=s.start,o=f.row-a.row,u=f.column-a.column;this.addFolds(i.map(function(e){return e=e.clone(),e.start.row==a.row&&(e.start.column+=u),e.end.row==a.row&&(e.end.column+=u),e.start.row+=o,e.end.row+=o,e}))}return s},e.prototype.indentRows=function(e,t,n){n=n.replace(/\t/g,this.getTabString());for(var r=e;r<=t;r++)this.doc.insertInLine({row:r,column:0},n)},e.prototype.outdentRows=function(e){var t=e.collapseRows(),n=new l(0,0,0,0),r=this.getTabSize();for(var i=t.start.row;i<=t.end.row;++i){var s=this.getLine(i);n.start.row=i,n.end.row=i;for(var o=0;o0){var r=this.getRowFoldEnd(t+n);if(r>this.doc.getLength()-1)return 0;var i=r-t}else{e=this.$clipRowToDocument(e),t=this.$clipRowToDocument(t);var i=t-e+1}var s=new l(e,0,t,Number.MAX_VALUE),o=this.getFoldsInRange(s).map(function(e){return e=e.clone(),e.start.row+=i,e.end.row+=i,e}),u=n==0?this.doc.getLines(e,t):this.doc.removeFullLines(e,t);return this.doc.insertFullLines(e+i,u),o.length&&this.addFolds(o),i},e.prototype.moveLinesUp=function(e,t){return this.$moveLines(e,t,-1)},e.prototype.moveLinesDown=function(e,t){return this.$moveLines(e,t,1)},e.prototype.duplicateLines=function(e,t){return this.$moveLines(e,t,0)},e.prototype.$clipRowToDocument=function(e){return Math.max(0,Math.min(e,this.doc.getLength()-1))},e.prototype.$clipColumnToRow=function(e,t){return t<0?0:Math.min(this.doc.getLine(e).length,t)},e.prototype.$clipPositionToDocument=function(e,t){t=Math.max(0,t);if(e<0)e=0,t=0;else{var n=this.doc.getLength();e>=n?(e=n-1,t=this.doc.getLine(n-1).length):t=Math.min(this.doc.getLine(e).length,t)}return{row:e,column:t}},e.prototype.$clipRangeToDocument=function(e){e.start.row<0?(e.start.row=0,e.start.column=0):e.start.column=this.$clipColumnToRow(e.start.row,e.start.column);var t=this.doc.getLength()-1;return e.end.row>t?(e.end.row=t,e.end.column=this.doc.getLine(t).length):e.end.column=this.$clipColumnToRow(e.end.row,e.end.column),e},e.prototype.setUseWrapMode=function(e){if(e!=this.$useWrapMode){this.$useWrapMode=e,this.$modified=!0,this.$resetRowCache(0);if(e){var t=this.getLength();this.$wrapData=Array(t),this.$updateWrapData(0,t-1)}this._signal("changeWrapMode")}},e.prototype.getUseWrapMode=function(){return this.$useWrapMode},e.prototype.setWrapLimitRange=function(e,t){if(this.$wrapLimitRange.min!==e||this.$wrapLimitRange.max!==t)this.$wrapLimitRange={min:e,max:t},this.$modified=!0,this.$bidiHandler.markAsDirty(),this.$useWrapMode&&this._signal("changeWrapMode")},e.prototype.adjustWrapLimit=function(e,t){var n=this.$wrapLimitRange;n.max<0&&(n={min:t,max:t});var r=this.$constrainWrapLimit(e,n.min,n.max);return r!=this.$wrapLimit&&r>1?(this.$wrapLimit=r,this.$modified=!0,this.$useWrapMode&&(this.$updateWrapData(0,this.getLength()-1),this.$resetRowCache(0),this._signal("changeWrapLimit")),!0):!1},e.prototype.$constrainWrapLimit=function(e,t,n){return t&&(e=Math.max(t,e)),n&&(e=Math.min(n,e)),e},e.prototype.getWrapLimit=function(){return this.$wrapLimit},e.prototype.setWrapLimit=function(e){this.setWrapLimitRange(e,e)},e.prototype.getWrapLimitRange=function(){return{min:this.$wrapLimitRange.min,max:this.$wrapLimitRange.max}},e.prototype.$updateInternalDataOnChange=function(e){var t=this.$useWrapMode,n=e.action,r=e.start,i=e.end,s=r.row,o=i.row,u=o-s,a=null;this.$updating=!0;if(u!=0)if(n==="remove"){this[t?"$wrapData":"$rowLengthCache"].splice(s,u);var f=this.$foldData;a=this.getFoldsInRange(e),this.removeFolds(a);var l=this.getFoldLine(i.row),c=0;if(l){l.addRemoveChars(i.row,i.column,r.column-i.column),l.shiftRow(-u);var h=this.getFoldLine(s);h&&h!==l&&(h.merge(l),l=h),c=f.indexOf(l)+1}for(c;c=i.row&&l.shiftRow(-u)}o=s}else{var p=Array(u);p.unshift(s,0);var d=t?this.$wrapData:this.$rowLengthCache;d.splice.apply(d,p);var f=this.$foldData,l=this.getFoldLine(s),c=0;if(l){var v=l.range.compareInside(r.row,r.column);v==0?(l=l.split(r.row,r.column),l&&(l.shiftRow(u),l.addRemoveChars(o,0,i.column-r.column))):v==-1&&(l.addRemoveChars(s,0,i.column-r.column),l.shiftRow(u)),c=f.indexOf(l)+1}for(c;c=s&&l.shiftRow(u)}}else{u=Math.abs(e.start.column-e.end.column),n==="remove"&&(a=this.getFoldsInRange(e),this.removeFolds(a),u=-u);var l=this.getFoldLine(s);l&&l.addRemoveChars(s,r.column,u)}return t&&this.$wrapData.length!=this.doc.getLength()&&console.error("doc.getLength() and $wrapData.length have to be the same!"),this.$updating=!1,t?this.$updateWrapData(s,o):this.$updateRowLengthCache(s,o),a},e.prototype.$updateRowLengthCache=function(e,t){this.$rowLengthCache[e]=null,this.$rowLengthCache[t]=null},e.prototype.$updateWrapData=function(e,t){var n=this.doc.getAllLines(),r=this.getTabSize(),i=this.$wrapData,s=this.$wrapLimit,o,u,a=e;t=Math.min(t,n.length-1);while(a<=t)u=this.getFoldLine(a,u),u?(o=[],u.walk(function(e,t,r,i){var s;if(e!=null){s=this.$getDisplayTokens(e,o.length),s[0]=y;for(var u=1;ut-h){var p=s+t-h;if(e[p-1]>=E&&e[p]>=E){c(p);continue}if(e[p]==y||e[p]==b){for(p;p!=s-1;p--)if(e[p]==y)break;if(p>s){c(p);continue}p=s+t;for(p;p>2)),s-1);while(p>d&&e[p]d&&e[p]d&&e[p]==w)p--}else while(p>d&&e[p]d){c(++p);continue}p=s+t,e[p]==g&&p--,c(p-h)}return r},e.prototype.$getDisplayTokens=function(e,t){var n=[],r;t=t||0;for(var i=0;i39&&s<48||s>57&&s<64?n.push(w):s>=4352&&T(s)?n.push(m,g):n.push(m)}return n},e.prototype.$getStringScreenWidth=function(e,t,n){if(t==0)return[0,0];t==null&&(t=Infinity),n=n||0;var r,i;for(i=0;i=4352&&T(r)?n+=2:n+=1;if(n>t)break}return[n,i]},e.prototype.getRowLength=function(e){var t=1;return this.lineWidgets&&(t+=this.lineWidgets[e]&&this.lineWidgets[e].rowCount||0),!this.$useWrapMode||!this.$wrapData[e]?t:this.$wrapData[e].length+t},e.prototype.getRowLineCount=function(e){return!this.$useWrapMode||!this.$wrapData[e]?1:this.$wrapData[e].length+1},e.prototype.getRowWrapIndent=function(e){if(this.$useWrapMode){var t=this.screenToDocumentPosition(e,Number.MAX_VALUE),n=this.$wrapData[t.row];return n.length&&n[0]=0)var u=f[l],i=this.$docRowCache[l],h=e>f[c-1];else var h=!c;var p=this.getLength()-1,d=this.getNextFoldLine(i),v=d?d.start.row:Infinity;while(u<=e){a=this.getRowLength(i);if(u+a>e||i>=p)break;u+=a,i++,i>v&&(i=d.end.row+1,d=this.getNextFoldLine(i,d),v=d?d.start.row:Infinity),h&&(this.$docRowCache.push(i),this.$screenRowCache.push(u))}if(d&&d.start.row<=i)r=this.getFoldDisplayLine(d),i=d.start.row;else{if(u+a<=e||i>p)return{row:p,column:this.getLine(p).length};r=this.getLine(i),d=null}var m=0,g=Math.floor(e-u);if(this.$useWrapMode){var y=this.$wrapData[i];y&&(o=y[g],g>0&&y.length&&(m=y.indent,s=y[g-1]||y[y.length-1],r=r.substring(s)))}return n!==undefined&&this.$bidiHandler.isBidiRow(u+g,i,g)&&(t=this.$bidiHandler.offsetToCol(n)),s+=this.$getStringScreenWidth(r,t-m)[1],this.$useWrapMode&&s>=o&&(s=o-1),d?d.idxToPosition(s):{row:i,column:s}},e.prototype.documentToScreenPosition=function(e,t){if(typeof t=="undefined")var n=this.$clipPositionToDocument(e.row,e.column);else n=this.$clipPositionToDocument(e,t);e=n.row,t=n.column;var r=0,i=null,s=null;s=this.getFoldAt(e,t,1),s&&(e=s.start.row,t=s.start.column);var o,u=0,a=this.$docRowCache,f=this.$getRowCacheIndex(a,e),l=a.length;if(l&&f>=0)var u=a[f],r=this.$screenRowCache[f],c=e>a[l-1];else var c=!l;var h=this.getNextFoldLine(u),p=h?h.start.row:Infinity;while(u=p){o=h.end.row+1;if(o>e)break;h=this.getNextFoldLine(o,h),p=h?h.start.row:Infinity}else o=u+1;r+=this.getRowLength(u),u=o,c&&(this.$docRowCache.push(u),this.$screenRowCache.push(r))}var d="";h&&u>=p?(d=this.getFoldDisplayLine(h,e,t),i=h.start.row):(d=this.getLine(e).substring(0,t),i=e);var v=0;if(this.$useWrapMode){var m=this.$wrapData[i];if(m){var g=0;while(d.length>=m[g])r++,g++;d=d.substring(m[g-1]||0,d.length),v=g>0?m.indent:0}}return this.lineWidgets&&this.lineWidgets[u]&&this.lineWidgets[u].rowsAbove&&(r+=this.lineWidgets[u].rowsAbove),{row:r,column:v+this.$getStringScreenWidth(d)[0]}},e.prototype.documentToScreenColumn=function(e,t){return this.documentToScreenPosition(e,t).column},e.prototype.documentToScreenRow=function(e,t){return this.documentToScreenPosition(e,t).row},e.prototype.getScreenLength=function(){var e=0,t=null;if(!this.$useWrapMode){e=this.getLength();var n=this.$foldData;for(var r=0;ro&&(s=t.end.row+1,t=this.$foldData[r++],o=t?t.start.row:Infinity)}}return this.lineWidgets&&(e+=this.$getWidgetScreenLength()),e},e.prototype.$setFontMetrics=function(e){if(!this.$enableVarChar)return;this.$getStringScreenWidth=function(t,n,r){if(n===0)return[0,0];n||(n=Infinity),r=r||0;var i,s;for(s=0;sn)break}return[r,s]}},e.prototype.destroy=function(){this.destroyed||(this.bgTokenizer.setDocument(null),this.bgTokenizer.cleanup(),this.destroyed=!0),this.$stopWorker(),this.removeAllListeners(),this.doc&&this.doc.off("change",this.$onChange),this.selection.detach()},e}();v.$uid=0,v.prototype.$modes=o.$modes,v.prototype.getValue=v.prototype.toString,v.prototype.$defaultUndoManager={undo:function(){},redo:function(){},hasUndo:function(){},hasRedo:function(){},reset:function(){},add:function(){},addSelection:function(){},startNewGroup:function(){},addSession:function(){}},v.prototype.$overwrite=!1,v.prototype.$mode=null,v.prototype.$modeId=null,v.prototype.$scrollTop=0,v.prototype.$scrollLeft=0,v.prototype.$wrapLimit=80,v.prototype.$useWrapMode=!1,v.prototype.$wrapLimitRange={min:null,max:null},v.prototype.lineWidgets=null,v.prototype.isFullWidth=T,r.implement(v.prototype,u);var m=1,g=2,y=3,b=4,w=9,E=10,S=11,x=12;e("./edit_session/folding").Folding.call(v.prototype),e("./edit_session/bracket_match").BracketMatch.call(v.prototype),o.defineOptions(v.prototype,"session",{wrap:{set:function(e){!e||e=="off"?e=!1:e=="free"?e=!0:e=="printMargin"?e=-1:typeof e=="string"&&(e=parseInt(e,10)||!1);if(this.$wrap==e)return;this.$wrap=e;if(!e)this.setUseWrapMode(!1);else{var t=typeof e=="number"?e:null;this.setWrapLimitRange(t,t),this.setUseWrapMode(!0)}},get:function(){return this.getUseWrapMode()?this.$wrap==-1?"printMargin":this.getWrapLimitRange().min?this.$wrap:"free":"off"},handlesSet:!0},wrapMethod:{set:function(e){e=e=="auto"?this.$mode.type!="text":e!="text",e!=this.$wrapAsCode&&(this.$wrapAsCode=e,this.$useWrapMode&&(this.$useWrapMode=!1,this.setUseWrapMode(!0)))},initialValue:"auto"},indentedSoftWrap:{set:function(){this.$useWrapMode&&(this.$useWrapMode=!1,this.setUseWrapMode(!0))},initialValue:!0},firstLineNumber:{set:function(){this._signal("changeBreakpoint")},initialValue:1},useWorker:{set:function(e){this.$useWorker=e,this.$stopWorker(),e&&this.$startWorker()},initialValue:!0},useSoftTabs:{initialValue:!0},tabSize:{set:function(e){e=parseInt(e),e>0&&this.$tabSize!==e&&(this.$modified=!0,this.$rowLengthCache=[],this.$tabSize=e,this._signal("changeTabSize"))},initialValue:4,handlesSet:!0},navigateWithinSoftTabs:{initialValue:!1},foldStyle:{set:function(e){this.setFoldStyle(e)},handlesSet:!0},overwrite:{set:function(e){this._signal("changeOverwrite")},initialValue:!1},newLineMode:{set:function(e){this.doc.setNewLineMode(e)},get:function(){return this.doc.getNewLineMode()},handlesSet:!0},mode:{set:function(e){this.setMode(e)},get:function(){return this.$modeId},handlesSet:!0}}),t.EditSession=v}),define("ace/search",["require","exports","module","ace/lib/lang","ace/lib/oop","ace/range"],function(e,t,n){"use strict";function u(e,t){function i(e,r){r===void 0&&(r=!0);var i=n&&t.$supportsUnicodeFlag?new RegExp("[\\p{L}\\p{N}_]","u"):new RegExp("\\w");if(i.test(e)||t.regExp)return n&&t.$supportsUnicodeFlag?r?"(?<=^|[^\\p{L}\\p{N}_])":"(?=[^\\p{L}\\p{N}_]|$)":"\\b";return""}var n=r.supportsLookbehind(),s=Array.from(e),o=s[0],u=s[s.length-1];return i(o)+e+i(u,!1)}var r=e("./lib/lang"),i=e("./lib/oop"),s=e("./range").Range,o=function(){function e(){this.$options={}}return e.prototype.set=function(e){return i.mixin(this.$options,e),this},e.prototype.getOptions=function(){return r.copyObject(this.$options)},e.prototype.setOptions=function(e){this.$options=e},e.prototype.find=function(e){var t=this.$options,n=this.$matchIterator(e,t);if(!n)return!1;var r=null;return n.forEach(function(e,n,i,o){return r=new s(e,n,i,o),n==o&&t.start&&t.start.start&&t.skipCurrent!=0&&r.isEqual(t.start)?(r=null,!1):!0}),r},e.prototype.findAll=function(e){var t=this.$options;if(!t.needle)return[];this.$assembleRegExp(t);var n=t.range,i=n?e.getLines(n.start.row,n.end.row):e.doc.getAllLines(),o=[],u=t.re;if(t.$isMultiLine){var a=u.length,f=i.length-a,l;e:for(var c=u.offset||0;c<=f;c++){for(var h=0;hv)continue;o.push(l=new s(c,v,c+a-1,m)),a>2&&(c=c+a-2)}}else for(var g=0;gE&&o[h].end.row==S)h--;o=o.slice(g,h+1);for(g=0,h=o.length;g=f;n--)if(p(n,Number.MAX_VALUE,e))return;if(t.wrap==0)return;for(n=l,f=a.row;n>=f;n--)if(p(n,Number.MAX_VALUE,e))return};else var c=function(e){var n=a.row;if(p(n,a.column,e))return;for(n+=1;n<=l;n++)if(p(n,0,e))return;if(t.wrap==0)return;for(n=f,l=a.row;n<=l;n++)if(p(n,0,e))return};if(t.$isMultiLine)var h=n.length,p=function(t,r,s){var o=i?t-h+1:t;if(o<0||o+h>e.getLength())return;var u=e.getLine(o),a=u.search(n[0]);if(!i&&ar)return;if(s(o,a,o+h-1,l))return!0};else if(i)var p=function(t,i,s){var u=e.getLine(t),a=[],f,l=0;n.lastIndex=0;while(f=n.exec(u)){var c=f[0].length;l=f.index;if(!c){if(l>=u.length)break;n.lastIndex=l+=r.skipEmptyMatch(u,l,o)}if(f.index+c>i)break;a.push(f.index,c)}for(var h=a.length-1;h>=0;h-=2){var p=a[h-1],c=a[h];if(s(t,p,t,p+c))return!0}};else var p=function(t,i,s){var u=e.getLine(t),a,f;n.lastIndex=i;while(f=n.exec(u)){var l=f[0].length;a=f.index;if(s(t,a,t,a+l))return!0;if(!l){n.lastIndex=a+=r.skipEmptyMatch(u,a,o);if(a>=u.length)return!1}}};return{forEach:c}},e}();t.Search=o}),define("ace/keyboard/hash_handler",["require","exports","module","ace/lib/keys","ace/lib/useragent"],function(e,t,n){"use strict";function a(e){return typeof e=="object"&&e.bindKey&&e.bindKey.position||(e.isDefault?-100:0)}var r=this&&this.__extends||function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},e(t,n)};return function(t,n){function r(){this.constructor=t}if(typeof n!="function"&&n!==null)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");e(t,n),t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),i=e("../lib/keys"),s=e("../lib/useragent"),o=i.KEY_MODS,u=function(){function e(e,t){this.$init(e,t,!1)}return e.prototype.$init=function(e,t,n){this.platform=t||(s.isMac?"mac":"win"),this.commands={},this.commandKeyBinding={},this.addCommands(e),this.$singleCommand=n},e.prototype.addCommand=function(e){this.commands[e.name]&&this.removeCommand(e),this.commands[e.name]=e,e.bindKey&&this._buildKeyHash(e)},e.prototype.removeCommand=function(e,t){var n=e&&(typeof e=="string"?e:e.name);e=this.commands[n],t||delete this.commands[n];var r=this.commandKeyBinding;for(var i in r){var s=r[i];if(s==e)delete r[i];else if(Array.isArray(s)){var o=s.indexOf(e);o!=-1&&(s.splice(o,1),s.length==1&&(r[i]=s[0]))}}},e.prototype.bindKey=function(e,t,n){typeof e=="object"&&e&&(n==undefined&&(n=e.position),e=e[this.platform]);if(!e)return;if(typeof t=="function")return this.addCommand({exec:t,bindKey:e,name:t.name||e});e.split("|").forEach(function(e){var r="";if(e.indexOf(" ")!=-1){var i=e.split(/\s+/);e=i.pop(),i.forEach(function(e){var t=this.parseKeys(e),n=o[t.hashId]+t.key;r+=(r?" ":"")+n,this._addCommandToBinding(r,"chainKeys")},this),r+=" "}var s=this.parseKeys(e),u=o[s.hashId]+s.key;this._addCommandToBinding(r+u,t,n)},this)},e.prototype._addCommandToBinding=function(e,t,n){var r=this.commandKeyBinding,i;if(!t)delete r[e];else if(!r[e]||this.$singleCommand)r[e]=t;else{Array.isArray(r[e])?(i=r[e].indexOf(t))!=-1&&r[e].splice(i,1):r[e]=[r[e]],typeof n!="number"&&(n=a(t));var s=r[e];for(i=0;in)break}s.splice(i,0,t)}},e.prototype.addCommands=function(e){e&&Object.keys(e).forEach(function(t){var n=e[t];if(!n)return;if(typeof n=="string")return this.bindKey(n,t);typeof n=="function"&&(n={exec:n});if(typeof n!="object")return;n.name||(n.name=t),this.addCommand(n)},this)},e.prototype.removeCommands=function(e){Object.keys(e).forEach(function(t){this.removeCommand(e[t])},this)},e.prototype.bindKeys=function(e){Object.keys(e).forEach(function(t){this.bindKey(t,e[t])},this)},e.prototype._buildKeyHash=function(e){this.bindKey(e.bindKey,e)},e.prototype.parseKeys=function(e){var t=e.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function(e){return e}),n=t.pop(),r=i[n];if(i.FUNCTION_KEYS[r])n=i.FUNCTION_KEYS[r].toLowerCase();else{if(!t.length)return{key:n,hashId:-1};if(t.length==1&&t[0]=="shift")return{key:n.toUpperCase(),hashId:-1}}var s=0;for(var o=t.length;o--;){var u=i.KEY_MODS[t[o]];if(u==null)return typeof console!="undefined"&&console.error("invalid modifier "+t[o]+" in "+e),!1;s|=u}return{key:n,hashId:s}},e.prototype.findKeyCommand=function(e,t){var n=o[e]+t;return this.commandKeyBinding[n]},e.prototype.handleKeyboard=function(e,t,n,r){if(r<0)return;var i=o[t]+n,s=this.commandKeyBinding[i];e.$keyChain&&(e.$keyChain+=" "+i,s=this.commandKeyBinding[e.$keyChain]||s);if(s)if(s=="chainKeys"||s[s.length-1]=="chainKeys")return e.$keyChain=e.$keyChain||i,{command:"null"};if(e.$keyChain)if(!!t&&t!=4||n.length!=1){if(t==-1||r>0)e.$keyChain=""}else e.$keyChain=e.$keyChain.slice(0,-i.length-1);return{command:s}},e.prototype.getStatusText=function(e,t){return t.$keyChain||""},e}(),f=function(e){function t(t,n){var r=e.call(this,t,n)||this;return r.$singleCommand=!0,r}return r(t,e),t}(u);f.call=function(e,t,n){u.prototype.$init.call(e,t,n,!0)},u.call=function(e,t,n){u.prototype.$init.call(e,t,n,!1)},t.HashHandler=f,t.MultiHashHandler=u}),define("ace/commands/command_manager",["require","exports","module","ace/lib/oop","ace/keyboard/hash_handler","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=this&&this.__extends||function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},e(t,n)};return function(t,n){function r(){this.constructor=t}if(typeof n!="function"&&n!==null)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");e(t,n),t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),i=e("../lib/oop"),s=e("../keyboard/hash_handler").MultiHashHandler,o=e("../lib/event_emitter").EventEmitter,u=function(e){function t(t,n){var r=e.call(this,n,t)||this;return r.byName=r.commands,r.setDefaultHandler("exec",function(e){return e.args?e.command.exec(e.editor,e.args,e.event,!1):e.command.exec(e.editor,{},e.event,!0)}),r}return r(t,e),t.prototype.exec=function(e,t,n){if(Array.isArray(e)){for(var r=e.length;r--;)if(this.exec(e[r],t,n))return!0;return!1}typeof e=="string"&&(e=this.commands[e]);if(!e)return!1;if(t&&t.$readOnly&&!e.readOnly)return!1;if(this.$checkCommandState!=0&&e.isAvailable&&!e.isAvailable(t))return!1;var i={editor:t,command:e,args:n};return i.returnValue=this._emit("exec",i),this._signal("afterExec",i),i.returnValue===!1?!1:!0},t.prototype.toggleRecording=function(e){if(this.$inReplay)return;return e&&e._emit("changeStatus"),this.recording?(this.macro.pop(),this.off("exec",this.$addCommandToMacro),this.macro.length||(this.macro=this.oldMacro),this.recording=!1):(this.$addCommandToMacro||(this.$addCommandToMacro=function(e){this.macro.push([e.command,e.args])}.bind(this)),this.oldMacro=this.macro,this.macro=[],this.on("exec",this.$addCommandToMacro),this.recording=!0)},t.prototype.replay=function(e){if(this.$inReplay||!this.macro)return;if(this.recording)return this.toggleRecording(e);try{this.$inReplay=!0,this.macro.forEach(function(t){typeof t=="string"?this.exec(t,e):this.exec(t[0],e,t[1])},this)}finally{this.$inReplay=!1}},t.prototype.trimMacro=function(e){return e.map(function(e){return typeof e[0]!="string"&&(e[0]=e[0].name),e[1]||(e=e[0]),e})},t}(s);i.implement(u.prototype,o),t.CommandManager=u}),define("ace/commands/default_commands",["require","exports","module","ace/lib/lang","ace/config","ace/range"],function(e,t,n){"use strict";function o(e,t){return{win:e,mac:t}}var r=e("../lib/lang"),i=e("../config"),s=e("../range").Range;t.commands=[{name:"showSettingsMenu",description:"Show settings menu",bindKey:o("Ctrl-,","Command-,"),exec:function(e){i.loadModule("ace/ext/settings_menu",function(t){t.init(e),e.showSettingsMenu()})},readOnly:!0},{name:"goToNextError",description:"Go to next error",bindKey:o("Alt-E","F4"),exec:function(e){i.loadModule("ace/ext/error_marker",function(t){t.showErrorMarker(e,1)})},scrollIntoView:"animate",readOnly:!0},{name:"goToPreviousError",description:"Go to previous error",bindKey:o("Alt-Shift-E","Shift-F4"),exec:function(e){i.loadModule("ace/ext/error_marker",function(t){t.showErrorMarker(e,-1)})},scrollIntoView:"animate",readOnly:!0},{name:"selectall",description:"Select all",bindKey:o("Ctrl-A","Command-A"),exec:function(e){e.selectAll()},readOnly:!0},{name:"centerselection",description:"Center selection",bindKey:o(null,"Ctrl-L"),exec:function(e){e.centerSelection()},readOnly:!0},{name:"gotoline",description:"Go to line...",bindKey:o("Ctrl-L","Command-L"),exec:function(e,t){typeof t=="number"&&!isNaN(t)&&e.gotoLine(t),e.prompt({$type:"gotoLine"})},readOnly:!0},{name:"fold",bindKey:o("Alt-L|Ctrl-F1","Command-Alt-L|Command-F1"),exec:function(e){e.session.toggleFold(!1)},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"unfold",bindKey:o("Alt-Shift-L|Ctrl-Shift-F1","Command-Alt-Shift-L|Command-Shift-F1"),exec:function(e){e.session.toggleFold(!0)},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"toggleFoldWidget",description:"Toggle fold widget",bindKey:o("F2","F2"),exec:function(e){e.session.toggleFoldWidget()},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"toggleParentFoldWidget",description:"Toggle parent fold widget",bindKey:o("Alt-F2","Alt-F2"),exec:function(e){e.session.toggleFoldWidget(!0)},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"foldall",description:"Fold all",bindKey:o(null,"Ctrl-Command-Option-0"),exec:function(e){e.session.foldAll()},scrollIntoView:"center",readOnly:!0},{name:"foldAllComments",description:"Fold all comments",bindKey:o(null,"Ctrl-Command-Option-0"),exec:function(e){e.session.foldAllComments()},scrollIntoView:"center",readOnly:!0},{name:"foldOther",description:"Fold other",bindKey:o("Alt-0","Command-Option-0"),exec:function(e){e.session.foldAll(),e.session.unfold(e.selection.getAllRanges())},scrollIntoView:"center",readOnly:!0},{name:"unfoldall",description:"Unfold all",bindKey:o("Alt-Shift-0","Command-Option-Shift-0"),exec:function(e){e.session.unfold()},scrollIntoView:"center",readOnly:!0},{name:"findnext",description:"Find next",bindKey:o("Ctrl-K","Command-G"),exec:function(e){e.findNext()},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"findprevious",description:"Find previous",bindKey:o("Ctrl-Shift-K","Command-Shift-G"),exec:function(e){e.findPrevious()},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"selectOrFindNext",description:"Select or find next",bindKey:o("Alt-K","Ctrl-G"),exec:function(e){e.selection.isEmpty()?e.selection.selectWord():e.findNext()},readOnly:!0},{name:"selectOrFindPrevious",description:"Select or find previous",bindKey:o("Alt-Shift-K","Ctrl-Shift-G"),exec:function(e){e.selection.isEmpty()?e.selection.selectWord():e.findPrevious()},readOnly:!0},{name:"find",description:"Find",bindKey:o("Ctrl-F","Command-F"),exec:function(e){i.loadModule("ace/ext/searchbox",function(t){t.Search(e)})},readOnly:!0},{name:"overwrite",description:"Overwrite",bindKey:"Insert",exec:function(e){e.toggleOverwrite()},readOnly:!0},{name:"selecttostart",description:"Select to start",bindKey:o("Ctrl-Shift-Home","Command-Shift-Home|Command-Shift-Up"),exec:function(e){e.getSelection().selectFileStart()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"gotostart",description:"Go to start",bindKey:o("Ctrl-Home","Command-Home|Command-Up"),exec:function(e){e.navigateFileStart()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"selectup",description:"Select up",bindKey:o("Shift-Up","Shift-Up|Ctrl-Shift-P"),exec:function(e){e.getSelection().selectUp()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"golineup",description:"Go line up",bindKey:o("Up","Up|Ctrl-P"),exec:function(e,t){e.navigateUp(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selecttoend",description:"Select to end",bindKey:o("Ctrl-Shift-End","Command-Shift-End|Command-Shift-Down"),exec:function(e){e.getSelection().selectFileEnd()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"gotoend",description:"Go to end",bindKey:o("Ctrl-End","Command-End|Command-Down"),exec:function(e){e.navigateFileEnd()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"selectdown",description:"Select down",bindKey:o("Shift-Down","Shift-Down|Ctrl-Shift-N"),exec:function(e){e.getSelection().selectDown()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"golinedown",description:"Go line down",bindKey:o("Down","Down|Ctrl-N"),exec:function(e,t){e.navigateDown(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectwordleft",description:"Select word left",bindKey:o("Ctrl-Shift-Left","Option-Shift-Left"),exec:function(e){e.getSelection().selectWordLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotowordleft",description:"Go to word left",bindKey:o("Ctrl-Left","Option-Left"),exec:function(e){e.navigateWordLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selecttolinestart",description:"Select to line start",bindKey:o("Alt-Shift-Left","Command-Shift-Left|Ctrl-Shift-A"),exec:function(e){e.getSelection().selectLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotolinestart",description:"Go to line start",bindKey:o("Alt-Left|Home","Command-Left|Home|Ctrl-A"),exec:function(e){e.navigateLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectleft",description:"Select left",bindKey:o("Shift-Left","Shift-Left|Ctrl-Shift-B"),exec:function(e){e.getSelection().selectLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotoleft",description:"Go to left",bindKey:o("Left","Left|Ctrl-B"),exec:function(e,t){e.navigateLeft(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectwordright",description:"Select word right",bindKey:o("Ctrl-Shift-Right","Option-Shift-Right"),exec:function(e){e.getSelection().selectWordRight()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotowordright",description:"Go to word right",bindKey:o("Ctrl-Right","Option-Right"),exec:function(e){e.navigateWordRight()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selecttolineend",description:"Select to line end",bindKey:o("Alt-Shift-Right","Command-Shift-Right|Shift-End|Ctrl-Shift-E"),exec:function(e){e.getSelection().selectLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotolineend",description:"Go to line end",bindKey:o("Alt-Right|End","Command-Right|End|Ctrl-E"),exec:function(e){e.navigateLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectright",description:"Select right",bindKey:o("Shift-Right","Shift-Right"),exec:function(e){e.getSelection().selectRight()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotoright",description:"Go to right",bindKey:o("Right","Right|Ctrl-F"),exec:function(e,t){e.navigateRight(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectpagedown",description:"Select page down",bindKey:"Shift-PageDown",exec:function(e){e.selectPageDown()},readOnly:!0},{name:"pagedown",description:"Page down",bindKey:o(null,"Option-PageDown"),exec:function(e){e.scrollPageDown()},readOnly:!0},{name:"gotopagedown",description:"Go to page down",bindKey:o("PageDown","PageDown|Ctrl-V"),exec:function(e){e.gotoPageDown()},readOnly:!0},{name:"selectpageup",description:"Select page up",bindKey:"Shift-PageUp",exec:function(e){e.selectPageUp()},readOnly:!0},{name:"pageup",description:"Page up",bindKey:o(null,"Option-PageUp"),exec:function(e){e.scrollPageUp()},readOnly:!0},{name:"gotopageup",description:"Go to page up",bindKey:"PageUp",exec:function(e){e.gotoPageUp()},readOnly:!0},{name:"scrollup",description:"Scroll up",bindKey:o("Ctrl-Up",null),exec:function(e){e.renderer.scrollBy(0,-2*e.renderer.layerConfig.lineHeight)},readOnly:!0},{name:"scrolldown",description:"Scroll down",bindKey:o("Ctrl-Down",null),exec:function(e){e.renderer.scrollBy(0,2*e.renderer.layerConfig.lineHeight)},readOnly:!0},{name:"selectlinestart",description:"Select line start",bindKey:"Shift-Home",exec:function(e){e.getSelection().selectLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectlineend",description:"Select line end",bindKey:"Shift-End",exec:function(e){e.getSelection().selectLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"togglerecording",description:"Toggle recording",bindKey:o("Ctrl-Alt-E","Command-Option-E"),exec:function(e){e.commands.toggleRecording(e)},readOnly:!0},{name:"replaymacro",description:"Replay macro",bindKey:o("Ctrl-Shift-E","Command-Shift-E"),exec:function(e){e.commands.replay(e)},readOnly:!0},{name:"jumptomatching",description:"Jump to matching",bindKey:o("Ctrl-\\|Ctrl-P","Command-\\"),exec:function(e){e.jumpToMatching()},multiSelectAction:"forEach",scrollIntoView:"animate",readOnly:!0},{name:"selecttomatching",description:"Select to matching",bindKey:o("Ctrl-Shift-\\|Ctrl-Shift-P","Command-Shift-\\"),exec:function(e){e.jumpToMatching(!0)},multiSelectAction:"forEach",scrollIntoView:"animate",readOnly:!0},{name:"expandToMatching",description:"Expand to matching",bindKey:o("Ctrl-Shift-M","Ctrl-Shift-M"),exec:function(e){e.jumpToMatching(!0,!0)},multiSelectAction:"forEach",scrollIntoView:"animate",readOnly:!0},{name:"passKeysToBrowser",description:"Pass keys to browser",bindKey:o(null,null),exec:function(){},passEvent:!0,readOnly:!0},{name:"copy",description:"Copy",exec:function(e){},readOnly:!0},{name:"cut",description:"Cut",exec:function(e){var t=e.$copyWithEmptySelection&&e.selection.isEmpty(),n=t?e.selection.getLineRange():e.selection.getRange();e._emit("cut",n),n.isEmpty()||e.session.remove(n),e.clearSelection()},scrollIntoView:"cursor",multiSelectAction:"forEach"},{name:"paste",description:"Paste",exec:function(e,t){e.$handlePaste(t)},scrollIntoView:"cursor"},{name:"removeline",description:"Remove line",bindKey:o("Ctrl-D","Command-D"),exec:function(e){e.removeLines()},scrollIntoView:"cursor",multiSelectAction:"forEachLine"},{name:"duplicateSelection",description:"Duplicate selection",bindKey:o("Ctrl-Shift-D","Command-Shift-D"),exec:function(e){e.duplicateSelection()},scrollIntoView:"cursor",multiSelectAction:"forEach"},{name:"sortlines",description:"Sort lines",bindKey:o("Ctrl-Alt-S","Command-Alt-S"),exec:function(e){e.sortLines()},scrollIntoView:"selection",multiSelectAction:"forEachLine"},{name:"togglecomment",description:"Toggle comment",bindKey:o("Ctrl-/","Command-/"),exec:function(e){e.toggleCommentLines()},multiSelectAction:"forEachLine",scrollIntoView:"selectionPart"},{name:"toggleBlockComment",description:"Toggle block comment",bindKey:o("Ctrl-Shift-/","Command-Shift-/"),exec:function(e){e.toggleBlockComment()},multiSelectAction:"forEach",scrollIntoView:"selectionPart"},{name:"modifyNumberUp",description:"Modify number up",bindKey:o("Ctrl-Shift-Up","Alt-Shift-Up"),exec:function(e){e.modifyNumber(1)},scrollIntoView:"cursor",multiSelectAction:"forEach"},{name:"modifyNumberDown",description:"Modify number down",bindKey:o("Ctrl-Shift-Down","Alt-Shift-Down"),exec:function(e){e.modifyNumber(-1)},scrollIntoView:"cursor",multiSelectAction:"forEach"},{name:"replace",description:"Replace",bindKey:o("Ctrl-H","Command-Option-F"),exec:function(e){i.loadModule("ace/ext/searchbox",function(t){t.Search(e,!0)})}},{name:"undo",description:"Undo",bindKey:o("Ctrl-Z","Command-Z"),exec:function(e){e.undo()}},{name:"redo",description:"Redo",bindKey:o("Ctrl-Shift-Z|Ctrl-Y","Command-Shift-Z|Command-Y"),exec:function(e){e.redo()}},{name:"copylinesup",description:"Copy lines up",bindKey:o("Alt-Shift-Up","Command-Option-Up"),exec:function(e){e.copyLinesUp()},scrollIntoView:"cursor"},{name:"movelinesup",description:"Move lines up",bindKey:o("Alt-Up","Option-Up"),exec:function(e){e.moveLinesUp()},scrollIntoView:"cursor"},{name:"copylinesdown",description:"Copy lines down",bindKey:o("Alt-Shift-Down","Command-Option-Down"),exec:function(e){e.copyLinesDown()},scrollIntoView:"cursor"},{name:"movelinesdown",description:"Move lines down",bindKey:o("Alt-Down","Option-Down"),exec:function(e){e.moveLinesDown()},scrollIntoView:"cursor"},{name:"del",description:"Delete",bindKey:o("Delete","Delete|Ctrl-D|Shift-Delete"),exec:function(e){e.remove("right")},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"backspace",description:"Backspace",bindKey:o("Shift-Backspace|Backspace","Ctrl-Backspace|Shift-Backspace|Backspace|Ctrl-H"),exec:function(e){e.remove("left")},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"cut_or_delete",description:"Cut or delete",bindKey:o("Shift-Delete",null),exec:function(e){if(!e.selection.isEmpty())return!1;e.remove("left")},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removetolinestart",description:"Remove to line start",bindKey:o("Alt-Backspace","Command-Backspace"),exec:function(e){e.removeToLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removetolineend",description:"Remove to line end",bindKey:o("Alt-Delete","Ctrl-K|Command-Delete"),exec:function(e){e.removeToLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removetolinestarthard",description:"Remove to line start hard",bindKey:o("Ctrl-Shift-Backspace",null),exec:function(e){var t=e.selection.getRange();t.start.column=0,e.session.remove(t)},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removetolineendhard",description:"Remove to line end hard",bindKey:o("Ctrl-Shift-Delete",null),exec:function(e){var t=e.selection.getRange();t.end.column=Number.MAX_VALUE,e.session.remove(t)},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removewordleft",description:"Remove word left",bindKey:o("Ctrl-Backspace","Alt-Backspace|Ctrl-Alt-Backspace"),exec:function(e){e.removeWordLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removewordright",description:"Remove word right",bindKey:o("Ctrl-Delete","Alt-Delete"),exec:function(e){e.removeWordRight()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"outdent",description:"Outdent",bindKey:o("Shift-Tab","Shift-Tab"),exec:function(e){e.blockOutdent()},multiSelectAction:"forEach",scrollIntoView:"selectionPart"},{name:"indent",description:"Indent",bindKey:o("Tab","Tab"),exec:function(e){e.indent()},multiSelectAction:"forEach",scrollIntoView:"selectionPart"},{name:"blockoutdent",description:"Block outdent",bindKey:o("Ctrl-[","Ctrl-["),exec:function(e){e.blockOutdent()},multiSelectAction:"forEachLine",scrollIntoView:"selectionPart"},{name:"blockindent",description:"Block indent",bindKey:o("Ctrl-]","Ctrl-]"),exec:function(e){e.blockIndent()},multiSelectAction:"forEachLine",scrollIntoView:"selectionPart"},{name:"insertstring",description:"Insert string",exec:function(e,t){e.insert(t)},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"inserttext",description:"Insert text",exec:function(e,t){e.insert(r.stringRepeat(t.text||"",t.times||1))},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"splitline",description:"Split line",bindKey:o(null,"Ctrl-O"),exec:function(e){e.splitLine()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"transposeletters",description:"Transpose letters",bindKey:o("Alt-Shift-X","Ctrl-T"),exec:function(e){e.transposeLetters()},multiSelectAction:function(e){e.transposeSelections(1)},scrollIntoView:"cursor"},{name:"touppercase",description:"To uppercase",bindKey:o("Ctrl-U","Ctrl-U"),exec:function(e){e.toUpperCase()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"tolowercase",description:"To lowercase",bindKey:o("Ctrl-Shift-U","Ctrl-Shift-U"),exec:function(e){e.toLowerCase()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"autoindent",description:"Auto Indent",bindKey:o(null,null),exec:function(e){e.autoIndent()},multiSelectAction:"forEachLine",scrollIntoView:"animate"},{name:"expandtoline",description:"Expand to line",bindKey:o("Ctrl-Shift-L","Command-Shift-L"),exec:function(e){var t=e.selection.getRange();t.start.column=t.end.column=0,t.end.row++,e.selection.setRange(t,!1)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"openlink",bindKey:o("Ctrl+F3","F3"),exec:function(e){e.openLink()}},{name:"joinlines",description:"Join lines",bindKey:o(null,null),exec:function(e){var t=e.selection.isBackwards(),n=t?e.selection.getSelectionLead():e.selection.getSelectionAnchor(),i=t?e.selection.getSelectionAnchor():e.selection.getSelectionLead(),o=e.session.doc.getLine(n.row).length,u=e.session.doc.getTextRange(e.selection.getRange()),a=u.replace(/\n\s*/," ").length,f=e.session.doc.getLine(n.row);for(var l=n.row+1;l<=i.row+1;l++){var c=r.stringTrimLeft(r.stringTrimRight(e.session.doc.getLine(l)));c.length!==0&&(c=" "+c),f+=c}i.row+10?(e.selection.moveCursorTo(n.row,n.column),e.selection.selectTo(n.row,n.column+a)):(o=e.session.doc.getLine(n.row).length>o?o+1:o,e.selection.moveCursorTo(n.row,o))},multiSelectAction:"forEach",readOnly:!0},{name:"invertSelection",description:"Invert selection",bindKey:o(null,null),exec:function(e){var t=e.session.doc.getLength()-1,n=e.session.doc.getLine(t).length,r=e.selection.rangeList.ranges,i=[];r.length<1&&(r=[e.selection.getRange()]);for(var o=0;ot[n].column&&n++,s.unshift(n,0),t.splice.apply(t,s),this.$updateRows()}},e.prototype.$updateRows=function(){var e=this.session.lineWidgets;if(!e)return;var t=!0;e.forEach(function(e,n){if(e){t=!1,e.row=n;while(e.$oldWidget)e.$oldWidget.row=n,e=e.$oldWidget}}),t&&(this.session.lineWidgets=null)},e.prototype.$registerLineWidget=function(e){this.session.lineWidgets||(this.session.lineWidgets=new Array(this.session.getLength()));var t=this.session.lineWidgets[e.row];return t&&(e.$oldWidget=t,t.el&&t.el.parentNode&&(t.el.parentNode.removeChild(t.el),t._inDocument=!1)),this.session.lineWidgets[e.row]=e,e},e.prototype.addLineWidget=function(e){this.$registerLineWidget(e),e.session=this.session;if(!this.editor)return e;var t=this.editor.renderer;e.html&&!e.el&&(e.el=r.createElement("div"),e.el.innerHTML=e.html),e.text&&!e.el&&(e.el=r.createElement("div"),e.el.textContent=e.text),e.el&&(r.addCssClass(e.el,"ace_lineWidgetContainer"),e.className&&r.addCssClass(e.el,e.className),e.el.style.position="absolute",e.el.style.zIndex="5",t.container.appendChild(e.el),e._inDocument=!0,e.coverGutter||(e.el.style.zIndex="3"),e.pixelHeight==null&&(e.pixelHeight=e.el.offsetHeight)),e.rowCount==null&&(e.rowCount=e.pixelHeight/t.layerConfig.lineHeight);var n=this.session.getFoldAt(e.row,0);e.$fold=n;if(n){var i=this.session.lineWidgets;e.row==n.end.row&&!i[n.start.row]?i[n.start.row]=e:e.hidden=!0}return this.session._emit("changeFold",{data:{start:{row:e.row}}}),this.$updateRows(),this.renderWidgets(null,t),this.onWidgetChanged(e),e},e.prototype.removeLineWidget=function(e){e._inDocument=!1,e.session=null,e.el&&e.el.parentNode&&e.el.parentNode.removeChild(e.el);if(e.editor&&e.editor.destroy)try{e.editor.destroy()}catch(t){}if(this.session.lineWidgets){var n=this.session.lineWidgets[e.row];if(n==e)this.session.lineWidgets[e.row]=e.$oldWidget,e.$oldWidget&&this.onWidgetChanged(e.$oldWidget);else while(n){if(n.$oldWidget==e){n.$oldWidget=e.$oldWidget;break}n=n.$oldWidget}}this.session._emit("changeFold",{data:{start:{row:e.row}}}),this.$updateRows()},e.prototype.getWidgetsAtRow=function(e){var t=this.session.lineWidgets,n=t&&t[e],r=[];while(n)r.push(n),n=n.$oldWidget;return r},e.prototype.onWidgetChanged=function(e){this.session._changedWidgets.push(e),this.editor&&this.editor.renderer.updateFull()},e.prototype.measureWidgets=function(e,t){var n=this.session._changedWidgets,r=t.layerConfig;if(!n||!n.length)return;var i=Infinity;for(var s=0;s0&&!r[i])i--;this.firstRow=n.firstRow,this.lastRow=n.lastRow,t.$cursorLayer.config=n;for(var o=i;o<=s;o++){var u=r[o];if(!u||!u.el)continue;if(u.hidden){u.el.style.top=-100-(u.pixelHeight||0)+"px";continue}u._inDocument||(u._inDocument=!0,t.container.appendChild(u.el));var a=t.$cursorLayer.getPixelPosition({row:o,column:0},!0).top;u.coverLine||(a+=n.lineHeight*this.session.getRowLineCount(u.row)),u.el.style.top=a-n.offset+"px";var f=u.coverGutter?0:t.gutterWidth;u.fixedWidth||(f-=t.scrollLeft),u.el.style.left=f+"px",u.fullWidth&&u.screenWidth&&(u.el.style.minWidth=n.width+2*n.padding+"px"),u.fixedWidth?u.el.style.right=t.scrollBar.getWidth()+"px":u.el.style.right=""}},e}();t.LineWidgets=i}),define("ace/keyboard/gutter_handler",["require","exports","module","ace/lib/keys","ace/mouse/default_gutter_handler"],function(e,t,n){"use strict";var r=e("../lib/keys"),i=e("../mouse/default_gutter_handler").GutterTooltip,s=function(){function e(e){this.editor=e,this.gutterLayer=e.renderer.$gutterLayer,this.element=e.renderer.$gutter,this.lines=e.renderer.$gutterLayer.$lines,this.activeRowIndex=null,this.activeLane=null,this.annotationTooltip=new i(this.editor)}return e.prototype.addListener=function(){this.element.addEventListener("keydown",this.$onGutterKeyDown.bind(this)),this.element.addEventListener("focusout",this.$blurGutter.bind(this)),this.editor.on("mousewheel",this.$blurGutter.bind(this))},e.prototype.removeListener=function(){this.element.removeEventListener("keydown",this.$onGutterKeyDown.bind(this)),this.element.removeEventListener("focusout",this.$blurGutter.bind(this)),this.editor.off("mousewheel",this.$blurGutter.bind(this))},e.prototype.$onGutterKeyDown=function(e){if(this.annotationTooltip.isOpen){e.preventDefault(),e.keyCode===r.escape&&this.annotationTooltip.hideTooltip();return}if(e.target===this.element){if(e.keyCode!=r["enter"])return;e.preventDefault();var t=this.editor.getCursorPosition().row;this.editor.isRowVisible(t)||this.editor.scrollToLine(t,!0,!0),setTimeout(function(){var e=this.$rowToRowIndex(this.gutterLayer.$cursorCell.row),t=this.$findNearestFoldWidget(e),n=this.$findNearestAnnotation(e);if(t===null&&n===null)return;if(t===null&&n!==null){this.activeRowIndex=n,this.activeLane="annotation",this.$focusAnnotation(this.activeRowIndex);return}if(t!==null&&n===null){this.activeRowIndex=t,this.activeLane="fold",this.$focusFoldWidget(this.activeRowIndex);return}if(Math.abs(n-e)0||e+t=0&&this.$isFoldWidgetVisible(e-t))return e-t;if(e+t<=this.lines.getLength()-1&&this.$isFoldWidgetVisible(e+t))return e+t}return null},e.prototype.$findNearestAnnotation=function(e){if(this.$isAnnotationVisible(e))return e;var t=0;while(e-t>0||e+t=0&&this.$isAnnotationVisible(e-t))return e-t;if(e+t<=this.lines.getLength()-1&&this.$isAnnotationVisible(e+t))return e+t}return null},e.prototype.$focusFoldWidget=function(e){if(e==null)return;var t=this.$getFoldWidget(e);t.classList.add(this.editor.renderer.keyboardFocusClassName),t.focus()},e.prototype.$focusAnnotation=function(e){if(e==null)return;var t=this.$getAnnotation(e);t.classList.add(this.editor.renderer.keyboardFocusClassName),t.focus()},e.prototype.$blurFoldWidget=function(e){var t=this.$getFoldWidget(e);t.classList.remove(this.editor.renderer.keyboardFocusClassName),t.blur()},e.prototype.$blurAnnotation=function(e){var t=this.$getAnnotation(e);t.classList.remove(this.editor.renderer.keyboardFocusClassName),t.blur()},e.prototype.$moveFoldWidgetUp=function(){var e=this.activeRowIndex;while(e>0){e--;if(this.$isFoldWidgetVisible(e)){this.$blurFoldWidget(this.activeRowIndex),this.activeRowIndex=e,this.$focusFoldWidget(this.activeRowIndex);return}}return},e.prototype.$moveFoldWidgetDown=function(){var e=this.activeRowIndex;while(e0){e--;if(this.$isAnnotationVisible(e)){this.$blurAnnotation(this.activeRowIndex),this.activeRowIndex=e,this.$focusAnnotation(this.activeRowIndex);return}}return},e.prototype.$moveAnnotationDown=function(){var e=this.activeRowIndex;while(e=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},i=e("./lib/oop"),s=e("./lib/dom"),o=e("./lib/lang"),u=e("./lib/useragent"),a=e("./keyboard/textinput").TextInput,f=e("./mouse/mouse_handler").MouseHandler,l=e("./mouse/fold_handler").FoldHandler,c=e("./keyboard/keybinding").KeyBinding,h=e("./edit_session").EditSession,p=e("./search").Search,d=e("./range").Range,v=e("./lib/event_emitter").EventEmitter,m=e("./commands/command_manager").CommandManager,g=e("./commands/default_commands").commands,y=e("./config"),b=e("./token_iterator").TokenIterator,w=e("./line_widgets").LineWidgets,E=e("./keyboard/gutter_handler").GutterKeyboardHandler,S=e("./config").nls,x=e("./clipboard"),T=e("./lib/keys"),N=function(){function e(t,n,r){this.session,this.$toDestroy=[];var i=t.getContainerElement();this.container=i,this.renderer=t,this.id="editor"+ ++e.$uid,this.commands=new m(u.isMac?"mac":"win",g),typeof document=="object"&&(this.textInput=new a(t.getTextAreaContainer(),this),this.renderer.textarea=this.textInput.getElement(),this.$mouseHandler=new f(this),new l(this)),this.keyBinding=new c(this),this.$search=(new p).set({wrap:!0}),this.$historyTracker=this.$historyTracker.bind(this),this.commands.on("exec",this.$historyTracker),this.$initOperationListeners(),this._$emitInputEvent=o.delayedCall(function(){this._signal("input",{}),this.session&&!this.session.destroyed&&this.session.bgTokenizer.scheduleStart()}.bind(this)),this.on("change",function(e,t){t._$emitInputEvent.schedule(31)}),this.setSession(n||r&&r.session||new h("")),y.resetOptions(this),r&&this.setOptions(r),y._signal("editor",this)}return e.prototype.$initOperationListeners=function(){this.commands.on("exec",this.startOperation.bind(this),!0),this.commands.on("afterExec",this.endOperation.bind(this),!0),this.$opResetTimer=o.delayedCall(this.endOperation.bind(this,!0)),this.on("change",function(){this.curOp||(this.startOperation(),this.curOp.selectionBefore=this.$lastSel),this.curOp.docChanged=!0}.bind(this),!0),this.on("changeSelection",function(){this.curOp||(this.startOperation(),this.curOp.selectionBefore=this.$lastSel),this.curOp.selectionChanged=!0}.bind(this),!0)},e.prototype.startOperation=function(e){if(this.curOp){if(!e||this.curOp.command)return;this.prevOp=this.curOp}e||(this.previousCommand=null,e={}),this.$opResetTimer.schedule(),this.curOp=this.session.curOp={command:e.command||{},args:e.args,scrollTop:this.renderer.scrollTop},this.curOp.selectionBefore=this.selection.toJSON()},e.prototype.endOperation=function(e){if(this.curOp&&this.session){if(e&&e.returnValue===!1||!this.session)return this.curOp=null;if(e==1&&this.curOp.command&&this.curOp.command.name=="mouse")return;this._signal("beforeEndOperation");if(!this.curOp)return;var t=this.curOp.command,n=t&&t.scrollIntoView;if(n){switch(n){case"center-animate":n="animate";case"center":this.renderer.scrollCursorIntoView(null,.5);break;case"animate":case"cursor":this.renderer.scrollCursorIntoView();break;case"selectionPart":var r=this.selection.getRange(),i=this.renderer.layerConfig;(r.start.row>=i.lastRow||r.end.row<=i.firstRow)&&this.renderer.scrollSelectionIntoView(this.selection.anchor,this.selection.lead);break;default:}n=="animate"&&this.renderer.animateScrolling(this.curOp.scrollTop)}var s=this.selection.toJSON();this.curOp.selectionAfter=s,this.$lastSel=this.selection.toJSON(),this.session.getUndoManager().addSelection(s),this.prevOp=this.curOp,this.curOp=null}},e.prototype.$historyTracker=function(e){if(!this.$mergeUndoDeltas)return;var t=this.prevOp,n=this.$mergeableCommands,r=t.command&&e.command.name==t.command.name;if(e.command.name=="insertstring"){var i=e.args;this.mergeNextCommand===undefined&&(this.mergeNextCommand=!0),r=r&&this.mergeNextCommand&&(!/\s/.test(i)||/\s/.test(t.args)),this.mergeNextCommand=!0}else r=r&&n.indexOf(e.command.name)!==-1;this.$mergeUndoDeltas!="always"&&Date.now()-this.sequenceStartTime>2e3&&(r=!1),r?this.session.mergeUndoDeltas=!0:n.indexOf(e.command.name)!==-1&&(this.sequenceStartTime=Date.now())},e.prototype.setKeyboardHandler=function(e,t){if(e&&typeof e=="string"&&e!="ace"){this.$keybindingId=e;var n=this;y.loadModule(["keybinding",e],function(r){n.$keybindingId==e&&n.keyBinding.setKeyboardHandler(r&&r.handler),t&&t()})}else this.$keybindingId=null,this.keyBinding.setKeyboardHandler(e),t&&t()},e.prototype.getKeyboardHandler=function(){return this.keyBinding.getKeyboardHandler()},e.prototype.setSession=function(e){if(this.session==e)return;this.curOp&&this.endOperation(),this.curOp={};var t=this.session;if(t){this.session.off("change",this.$onDocumentChange),this.session.off("changeMode",this.$onChangeMode),this.session.off("tokenizerUpdate",this.$onTokenizerUpdate),this.session.off("changeTabSize",this.$onChangeTabSize),this.session.off("changeWrapLimit",this.$onChangeWrapLimit),this.session.off("changeWrapMode",this.$onChangeWrapMode),this.session.off("changeFold",this.$onChangeFold),this.session.off("changeFrontMarker",this.$onChangeFrontMarker),this.session.off("changeBackMarker",this.$onChangeBackMarker),this.session.off("changeBreakpoint",this.$onChangeBreakpoint),this.session.off("changeAnnotation",this.$onChangeAnnotation),this.session.off("changeOverwrite",this.$onCursorChange),this.session.off("changeScrollTop",this.$onScrollTopChange),this.session.off("changeScrollLeft",this.$onScrollLeftChange);var n=this.session.getSelection();n.off("changeCursor",this.$onCursorChange),n.off("changeSelection",this.$onSelectionChange)}this.session=e,e?(this.$onDocumentChange=this.onDocumentChange.bind(this),e.on("change",this.$onDocumentChange),this.renderer.setSession(e),this.$onChangeMode=this.onChangeMode.bind(this),e.on("changeMode",this.$onChangeMode),this.$onTokenizerUpdate=this.onTokenizerUpdate.bind(this),e.on("tokenizerUpdate",this.$onTokenizerUpdate),this.$onChangeTabSize=this.renderer.onChangeTabSize.bind(this.renderer),e.on("changeTabSize",this.$onChangeTabSize),this.$onChangeWrapLimit=this.onChangeWrapLimit.bind(this),e.on("changeWrapLimit",this.$onChangeWrapLimit),this.$onChangeWrapMode=this.onChangeWrapMode.bind(this),e.on("changeWrapMode",this.$onChangeWrapMode),this.$onChangeFold=this.onChangeFold.bind(this),e.on("changeFold",this.$onChangeFold),this.$onChangeFrontMarker=this.onChangeFrontMarker.bind(this),this.session.on("changeFrontMarker",this.$onChangeFrontMarker),this.$onChangeBackMarker=this.onChangeBackMarker.bind(this),this.session.on("changeBackMarker",this.$onChangeBackMarker),this.$onChangeBreakpoint=this.onChangeBreakpoint.bind(this),this.session.on("changeBreakpoint",this.$onChangeBreakpoint),this.$onChangeAnnotation=this.onChangeAnnotation.bind(this),this.session.on("changeAnnotation",this.$onChangeAnnotation),this.$onCursorChange=this.onCursorChange.bind(this),this.session.on("changeOverwrite",this.$onCursorChange),this.$onScrollTopChange=this.onScrollTopChange.bind(this),this.session.on("changeScrollTop",this.$onScrollTopChange),this.$onScrollLeftChange=this.onScrollLeftChange.bind(this),this.session.on("changeScrollLeft",this.$onScrollLeftChange),this.selection=e.getSelection(),this.selection.on("changeCursor",this.$onCursorChange),this.$onSelectionChange=this.onSelectionChange.bind(this),this.selection.on("changeSelection",this.$onSelectionChange),this.onChangeMode(),this.onCursorChange(),this.onScrollTopChange(),this.onScrollLeftChange(),this.onSelectionChange(),this.onChangeFrontMarker(),this.onChangeBackMarker(),this.onChangeBreakpoint(),this.onChangeAnnotation(),this.session.getUseWrapMode()&&this.renderer.adjustWrapLimit(),this.renderer.updateFull()):(this.selection=null,this.renderer.setSession(e)),this._signal("changeSession",{session:e,oldSession:t}),this.curOp=null,t&&t._signal("changeEditor",{oldEditor:this}),e&&e._signal("changeEditor",{editor:this}),e&&!e.destroyed&&e.bgTokenizer.scheduleStart()},e.prototype.getSession=function(){return this.session},e.prototype.setValue=function(e,t){return this.session.doc.setValue(e),t?t==1?this.navigateFileEnd():t==-1&&this.navigateFileStart():this.selectAll(),e},e.prototype.getValue=function(){return this.session.getValue()},e.prototype.getSelection=function(){return this.selection},e.prototype.resize=function(e){this.renderer.onResize(e)},e.prototype.setTheme=function(e,t){this.renderer.setTheme(e,t)},e.prototype.getTheme=function(){return this.renderer.getTheme()},e.prototype.setStyle=function(e){this.renderer.setStyle(e)},e.prototype.unsetStyle=function(e){this.renderer.unsetStyle(e)},e.prototype.getFontSize=function(){return this.getOption("fontSize")||s.computedStyle(this.container).fontSize},e.prototype.setFontSize=function(e){this.setOption("fontSize",e)},e.prototype.$highlightBrackets=function(){if(this.$highlightPending)return;var e=this;this.$highlightPending=!0,setTimeout(function(){e.$highlightPending=!1;var t=e.session;if(!t||t.destroyed)return;t.$bracketHighlight&&(t.$bracketHighlight.markerIds.forEach(function(e){t.removeMarker(e)}),t.$bracketHighlight=null);var n=e.getCursorPosition(),r=e.getKeyboardHandler(),i=r&&r.$getDirectionForHighlight&&r.$getDirectionForHighlight(e),s=t.getMatchingBracketRanges(n,i);if(!s){var o=new b(t,n.row,n.column),u=o.getCurrentToken();if(u&&/\b(?:tag-open|tag-name)/.test(u.type)){var a=t.getMatchingTags(n);a&&(s=[a.openTagName,a.closeTagName])}}!s&&t.$mode.getMatching&&(s=t.$mode.getMatching(e.session));if(!s){e.getHighlightIndentGuides()&&e.renderer.$textLayer.$highlightIndentGuide();return}var f="ace_bracket";Array.isArray(s)?s.length==1&&(f="ace_error_bracket"):s=[s],s.length==2&&(d.comparePoints(s[0].end,s[1].start)==0?s=[d.fromPoints(s[0].start,s[1].end)]:d.comparePoints(s[0].start,s[1].end)==0&&(s=[d.fromPoints(s[1].start,s[0].end)])),t.$bracketHighlight={ranges:s,markerIds:s.map(function(e){return t.addMarker(e,f,"text")})},e.getHighlightIndentGuides()&&e.renderer.$textLayer.$highlightIndentGuide()},50)},e.prototype.focus=function(){this.textInput.focus()},e.prototype.isFocused=function(){return this.textInput.isFocused()},e.prototype.blur=function(){this.textInput.blur()},e.prototype.onFocus=function(e){if(this.$isFocused)return;this.$isFocused=!0,this.renderer.showCursor(),this.renderer.visualizeFocus(),this._emit("focus",e)},e.prototype.onBlur=function(e){if(!this.$isFocused)return;this.$isFocused=!1,this.renderer.hideCursor(),this.renderer.visualizeBlur(),this._emit("blur",e)},e.prototype.$cursorChange=function(){this.renderer.updateCursor(),this.$highlightBrackets(),this.$updateHighlightActiveLine()},e.prototype.onDocumentChange=function(e){var t=this.session.$useWrapMode,n=e.start.row==e.end.row?e.end.row:Infinity;this.renderer.updateLines(e.start.row,n,t),this._signal("change",e),this.$cursorChange()},e.prototype.onTokenizerUpdate=function(e){var t=e.data;this.renderer.updateLines(t.first,t.last)},e.prototype.onScrollTopChange=function(){this.renderer.scrollToY(this.session.getScrollTop())},e.prototype.onScrollLeftChange=function(){this.renderer.scrollToX(this.session.getScrollLeft())},e.prototype.onCursorChange=function(){this.$cursorChange(),this._signal("changeSelection")},e.prototype.$updateHighlightActiveLine=function(){var e=this.getSession(),t;if(this.$highlightActiveLine){if(this.$selectionStyle!="line"||!this.selection.isMultiLine())t=this.getCursorPosition();this.renderer.theme&&this.renderer.theme.$selectionColorConflict&&!this.selection.isEmpty()&&(t=!1),this.renderer.$maxLines&&this.session.getLength()===1&&!(this.renderer.$minLines>1)&&(t=!1)}if(e.$highlightLineMarker&&!t)e.removeMarker(e.$highlightLineMarker.id),e.$highlightLineMarker=null;else if(!e.$highlightLineMarker&&t){var n=new d(t.row,t.column,t.row,Infinity);n.id=e.addMarker(n,"ace_active-line","screenLine"),e.$highlightLineMarker=n}else t&&(e.$highlightLineMarker.start.row=t.row,e.$highlightLineMarker.end.row=t.row,e.$highlightLineMarker.start.column=t.column,e._signal("changeBackMarker"))},e.prototype.onSelectionChange=function(e){var t=this.session;t.$selectionMarker&&t.removeMarker(t.$selectionMarker),t.$selectionMarker=null;if(!this.selection.isEmpty()){var n=this.selection.getRange(),r=this.getSelectionStyle();t.$selectionMarker=t.addMarker(n,"ace_selection",r)}else this.$updateHighlightActiveLine();var i=this.$highlightSelectedWord&&this.$getSelectionHighLightRegexp();this.session.highlight(i),this._signal("changeSelection")},e.prototype.$getSelectionHighLightRegexp=function(){var e=this.session,t=this.getSelectionRange();if(t.isEmpty()||t.isMultiLine())return;var n=t.start.column,r=t.end.column,i=e.getLine(t.start.row),s=i.substring(n,r);if(s.length>5e3||!/[\w\d]/.test(s))return;var o=this.$search.$assembleRegExp({wholeWord:!0,caseSensitive:!0,needle:s}),u=i.substring(n-1,r+1);if(!o.test(u))return;return o},e.prototype.onChangeFrontMarker=function(){this.renderer.updateFrontMarkers()},e.prototype.onChangeBackMarker=function(){this.renderer.updateBackMarkers()},e.prototype.onChangeBreakpoint=function(){this.renderer.updateBreakpoints()},e.prototype.onChangeAnnotation=function(){this.renderer.setAnnotations(this.session.getAnnotations())},e.prototype.onChangeMode=function(e){this.renderer.updateText(),this._emit("changeMode",e)},e.prototype.onChangeWrapLimit=function(){this.renderer.updateFull()},e.prototype.onChangeWrapMode=function(){this.renderer.onResize(!0)},e.prototype.onChangeFold=function(){this.$updateHighlightActiveLine(),this.renderer.updateFull()},e.prototype.getSelectedText=function(){return this.session.getTextRange(this.getSelectionRange())},e.prototype.getCopyText=function(){var e=this.getSelectedText(),t=this.session.doc.getNewLineCharacter(),n=!1;if(!e&&this.$copyWithEmptySelection){n=!0;var r=this.selection.getAllRanges();for(var i=0;iu.search(/\S|$/)){var a=u.substr(i.column).search(/\S|$/);n.doc.removeInLine(i.row,i.column,i.column+a)}}this.clearSelection();var f=i.column,l=n.getState(i.row),u=n.getLine(i.row),c=r.checkOutdent(l,u,e);n.insert(i,e),s&&s.selection&&(s.selection.length==2?this.selection.setSelectionRange(new d(i.row,f+s.selection[0],i.row,f+s.selection[1])):this.selection.setSelectionRange(new d(i.row+s.selection[0],s.selection[1],i.row+s.selection[2],s.selection[3])));if(this.$enableAutoIndent){if(n.getDocument().isNewLine(e)){var h=r.getNextLineIndent(l,u.slice(0,i.column),n.getTabString());n.insert({row:i.row+1,column:0},h)}c&&r.autoOutdent(l,n,i.row)}},e.prototype.autoIndent=function(){var e=this.session,t=e.getMode(),n,r;if(this.selection.isEmpty())n=0,r=e.doc.getLength()-1;else{var i=this.getSelectionRange();n=i.start.row,r=i.end.row}var s="",o="",u="",a,f,l,c=e.getTabString();for(var h=n;h<=r;h++)h>0&&(s=e.getState(h-1),o=e.getLine(h-1),u=t.getNextLineIndent(s,o,c)),a=e.getLine(h),f=t.$getIndent(a),u!==f&&(f.length>0&&(l=new d(h,0,h,f.length),e.remove(l)),u.length>0&&e.insert({row:h,column:0},u)),t.autoOutdent(s,e,h)},e.prototype.onTextInput=function(e,t){if(!t)return this.keyBinding.onTextInput(e);this.startOperation({command:{name:"insertstring"}});var n=this.applyComposition.bind(this,e,t);this.selection.rangeCount?this.forEachSelection(n):n(),this.endOperation()},e.prototype.applyComposition=function(e,t){if(t.extendLeft||t.extendRight){var n=this.selection.getRange();n.start.column-=t.extendLeft,n.end.column+=t.extendRight,n.start.column<0&&(n.start.row--,n.start.column+=this.session.getLine(n.start.row).length+1),this.selection.setRange(n),!e&&!n.isEmpty()&&this.remove()}(e||!this.selection.isEmpty())&&this.insert(e,!0);if(t.restoreStart||t.restoreEnd){var n=this.selection.getRange();n.start.column-=t.restoreStart,n.end.column-=t.restoreEnd,this.selection.setRange(n)}},e.prototype.onCommandKey=function(e,t,n){return this.keyBinding.onCommandKey(e,t,n)},e.prototype.setOverwrite=function(e){this.session.setOverwrite(e)},e.prototype.getOverwrite=function(){return this.session.getOverwrite()},e.prototype.toggleOverwrite=function(){this.session.toggleOverwrite()},e.prototype.setScrollSpeed=function(e){this.setOption("scrollSpeed",e)},e.prototype.getScrollSpeed=function(){return this.getOption("scrollSpeed")},e.prototype.setDragDelay=function(e){this.setOption("dragDelay",e)},e.prototype.getDragDelay=function(){return this.getOption("dragDelay")},e.prototype.setSelectionStyle=function(e){this.setOption("selectionStyle",e)},e.prototype.getSelectionStyle=function(){return this.getOption("selectionStyle")},e.prototype.setHighlightActiveLine=function(e){this.setOption("highlightActiveLine",e)},e.prototype.getHighlightActiveLine=function(){return this.getOption("highlightActiveLine")},e.prototype.setHighlightGutterLine=function(e){this.setOption("highlightGutterLine",e)},e.prototype.getHighlightGutterLine=function(){return this.getOption("highlightGutterLine")},e.prototype.setHighlightSelectedWord=function(e){this.setOption("highlightSelectedWord",e)},e.prototype.getHighlightSelectedWord=function(){return this.$highlightSelectedWord},e.prototype.setAnimatedScroll=function(e){this.renderer.setAnimatedScroll(e)},e.prototype.getAnimatedScroll=function(){return this.renderer.getAnimatedScroll()},e.prototype.setShowInvisibles=function(e){this.renderer.setShowInvisibles(e)},e.prototype.getShowInvisibles=function(){return this.renderer.getShowInvisibles()},e.prototype.setDisplayIndentGuides=function(e){this.renderer.setDisplayIndentGuides(e)},e.prototype.getDisplayIndentGuides=function(){return this.renderer.getDisplayIndentGuides()},e.prototype.setHighlightIndentGuides=function(e){this.renderer.setHighlightIndentGuides(e)},e.prototype.getHighlightIndentGuides=function(){return this.renderer.getHighlightIndentGuides()},e.prototype.setShowPrintMargin=function(e){this.renderer.setShowPrintMargin(e)},e.prototype.getShowPrintMargin=function(){return this.renderer.getShowPrintMargin()},e.prototype.setPrintMarginColumn=function(e){this.renderer.setPrintMarginColumn(e)},e.prototype.getPrintMarginColumn=function(){return this.renderer.getPrintMarginColumn()},e.prototype.setReadOnly=function(e){this.setOption("readOnly",e)},e.prototype.getReadOnly=function(){return this.getOption("readOnly")},e.prototype.setBehavioursEnabled=function(e){this.setOption("behavioursEnabled",e)},e.prototype.getBehavioursEnabled=function(){return this.getOption("behavioursEnabled")},e.prototype.setWrapBehavioursEnabled=function(e){this.setOption("wrapBehavioursEnabled",e)},e.prototype.getWrapBehavioursEnabled=function(){return this.getOption("wrapBehavioursEnabled")},e.prototype.setShowFoldWidgets=function(e){this.setOption("showFoldWidgets",e)},e.prototype.getShowFoldWidgets=function(){return this.getOption("showFoldWidgets")},e.prototype.setFadeFoldWidgets=function(e){this.setOption("fadeFoldWidgets",e)},e.prototype.getFadeFoldWidgets=function(){return this.getOption("fadeFoldWidgets")},e.prototype.remove=function(e){this.selection.isEmpty()&&(e=="left"?this.selection.selectLeft():this.selection.selectRight());var t=this.getSelectionRange();if(this.getBehavioursEnabled()){var n=this.session,r=n.getState(t.start.row),i=n.getMode().transformAction(r,"deletion",this,n,t);if(t.end.column===0){var s=n.getTextRange(t);if(s[s.length-1]=="\n"){var o=n.getLine(t.end.row);/^\s+$/.test(o)&&(t.end.column=o.length)}}i&&(t=i)}this.session.remove(t),this.clearSelection()},e.prototype.removeWordRight=function(){this.selection.isEmpty()&&this.selection.selectWordRight(),this.session.remove(this.getSelectionRange()),this.clearSelection()},e.prototype.removeWordLeft=function(){this.selection.isEmpty()&&this.selection.selectWordLeft(),this.session.remove(this.getSelectionRange()),this.clearSelection()},e.prototype.removeToLineStart=function(){this.selection.isEmpty()&&this.selection.selectLineStart(),this.selection.isEmpty()&&this.selection.selectLeft(),this.session.remove(this.getSelectionRange()),this.clearSelection()},e.prototype.removeToLineEnd=function(){this.selection.isEmpty()&&this.selection.selectLineEnd();var e=this.getSelectionRange();e.start.column==e.end.column&&e.start.row==e.end.row&&(e.end.column=0,e.end.row++),this.session.remove(e),this.clearSelection()},e.prototype.splitLine=function(){this.selection.isEmpty()||(this.session.remove(this.getSelectionRange()),this.clearSelection());var e=this.getCursorPosition();this.insert("\n"),this.moveCursorToPosition(e)},e.prototype.setGhostText=function(e,t){this.session.widgetManager||(this.session.widgetManager=new w(this.session),this.session.widgetManager.attach(this)),this.renderer.setGhostText(e,t)},e.prototype.removeGhostText=function(){if(!this.session.widgetManager)return;this.renderer.removeGhostText()},e.prototype.transposeLetters=function(){if(!this.selection.isEmpty())return;var e=this.getCursorPosition(),t=e.column;if(t===0)return;var n=this.session.getLine(e.row),r,i;tt.toLowerCase()?1:0});var i=new d(0,0,0,0);for(var r=e.first;r<=e.last;r++){var s=t.getLine(r);i.start.row=r,i.end.row=r,i.end.column=s.length,t.replace(i,n[r-e.first])}},e.prototype.toggleCommentLines=function(){var e=this.session.getState(this.getCursorPosition().row),t=this.$getSelectedRows();this.session.getMode().toggleCommentLines(e,this.session,t.first,t.last)},e.prototype.toggleBlockComment=function(){var e=this.getCursorPosition(),t=this.session.getState(e.row),n=this.getSelectionRange();this.session.getMode().toggleBlockComment(t,this.session,n,e)},e.prototype.getNumberAt=function(e,t){var n=/[\-]?[0-9]+(?:\.[0-9]+)?/g;n.lastIndex=0;var r=this.session.getLine(e);while(n.lastIndex=t){var s={value:i[0],start:i.index,end:i.index+i[0].length};return s}}return null},e.prototype.modifyNumber=function(e){var t=this.selection.getCursor().row,n=this.selection.getCursor().column,r=new d(t,n-1,t,n),i=this.session.getTextRange(r);if(!isNaN(parseFloat(i))&&isFinite(i)){var s=this.getNumberAt(t,n);if(s){var o=s.value.indexOf(".")>=0?s.start+s.value.indexOf(".")+1:s.end,u=s.start+s.value.length-o,a=parseFloat(s.value);a*=Math.pow(10,u),o!==s.end&&n=u&&s<=a&&(n=t,f.selection.clearSelection(),f.moveCursorTo(e,u+r),f.selection.selectTo(e,a+r)),u=a});var l=this.$toggleWordPairs,c;for(var h=0;h=a&&u<=f&&p.match(/((?:https?|ftp):\/\/[\S]+)/)){l=p.replace(/[\s:.,'";}\]]+$/,"");break}a=f}}catch(d){n={error:d}}finally{try{h&&!h.done&&(i=c.return)&&i.call(c)}finally{if(n)throw n.error}}return l},e.prototype.openLink=function(){var e=this.selection.getCursor(),t=this.findLinkAt(e.row,e.column);return t&&window.open(t,"_blank"),t!=null},e.prototype.removeLines=function(){var e=this.$getSelectedRows();this.session.removeFullLines(e.first,e.last),this.clearSelection()},e.prototype.duplicateSelection=function(){var e=this.selection,t=this.session,n=e.getRange(),r=e.isBackwards();if(n.isEmpty()){var i=n.start.row;t.duplicateLines(i,i)}else{var s=r?n.start:n.end,o=t.insert(s,t.getTextRange(n));n.start=s,n.end=o,e.setSelectionRange(n,r)}},e.prototype.moveLinesDown=function(){this.$moveLines(1,!1)},e.prototype.moveLinesUp=function(){this.$moveLines(-1,!1)},e.prototype.moveText=function(e,t,n){return this.session.moveText(e,t,n)},e.prototype.copyLinesUp=function(){this.$moveLines(-1,!0)},e.prototype.copyLinesDown=function(){this.$moveLines(1,!0)},e.prototype.$moveLines=function(e,t){var n,r,i=this.selection;if(!i.inMultiSelectMode||this.inVirtualSelectionMode){var s=i.toOrientedRange();n=this.$getSelectedRows(s),r=this.session.$moveLines(n.first,n.last,t?0:e),t&&e==-1&&(r=0),s.moveBy(r,0),i.fromOrientedRange(s)}else{var o=i.rangeList.ranges;i.rangeList.detach(this.session),this.inVirtualSelectionMode=!0;var u=0,a=0,f=o.length;for(var l=0;lp+1)break;p=d.last}l--,u=this.session.$moveLines(h,p,t?0:e),t&&e==-1&&(c=l+1);while(c<=l)o[c].moveBy(u,0),c++;t||(u=0),a+=u}i.fromOrientedRange(i.ranges[0]),i.rangeList.attach(this.session),this.inVirtualSelectionMode=!1}},e.prototype.$getSelectedRows=function(e){return e=(e||this.getSelectionRange()).collapseRows(),{first:this.session.getRowFoldStart(e.start.row),last:this.session.getRowFoldEnd(e.end.row)}},e.prototype.onCompositionStart=function(e){this.renderer.showComposition(e)},e.prototype.onCompositionUpdate=function(e){this.renderer.setCompositionText(e)},e.prototype.onCompositionEnd=function(){this.renderer.hideComposition()},e.prototype.getFirstVisibleRow=function(){return this.renderer.getFirstVisibleRow()},e.prototype.getLastVisibleRow=function(){return this.renderer.getLastVisibleRow()},e.prototype.isRowVisible=function(e){return e>=this.getFirstVisibleRow()&&e<=this.getLastVisibleRow()},e.prototype.isRowFullyVisible=function(e){return e>=this.renderer.getFirstFullyVisibleRow()&&e<=this.renderer.getLastFullyVisibleRow()},e.prototype.$getVisibleRowCount=function(){return this.renderer.getScrollBottomRow()-this.renderer.getScrollTopRow()+1},e.prototype.$moveByPage=function(e,t){var n=this.renderer,r=this.renderer.layerConfig,i=e*Math.floor(r.height/r.lineHeight);t===!0?this.selection.$moveSelection(function(){this.moveCursorBy(i,0)}):t===!1&&(this.selection.moveCursorBy(i,0),this.selection.clearSelection());var s=n.scrollTop;n.scrollBy(0,i*r.lineHeight),t!=null&&n.scrollCursorIntoView(null,.5),n.animateScrolling(s)},e.prototype.selectPageDown=function(){this.$moveByPage(1,!0)},e.prototype.selectPageUp=function(){this.$moveByPage(-1,!0)},e.prototype.gotoPageDown=function(){this.$moveByPage(1,!1)},e.prototype.gotoPageUp=function(){this.$moveByPage(-1,!1)},e.prototype.scrollPageDown=function(){this.$moveByPage(1)},e.prototype.scrollPageUp=function(){this.$moveByPage(-1)},e.prototype.scrollToRow=function(e){this.renderer.scrollToRow(e)},e.prototype.scrollToLine=function(e,t,n,r){this.renderer.scrollToLine(e,t,n,r)},e.prototype.centerSelection=function(){var e=this.getSelectionRange(),t={row:Math.floor(e.start.row+(e.end.row-e.start.row)/2),column:Math.floor(e.start.column+(e.end.column-e.start.column)/2)};this.renderer.alignCursor(t,.5)},e.prototype.getCursorPosition=function(){return this.selection.getCursor()},e.prototype.getCursorPositionScreen=function(){return this.session.documentToScreenPosition(this.getCursorPosition())},e.prototype.getSelectionRange=function(){return this.selection.getRange()},e.prototype.selectAll=function(){this.selection.selectAll()},e.prototype.clearSelection=function(){this.selection.clearSelection()},e.prototype.moveCursorTo=function(e,t){this.selection.moveCursorTo(e,t)},e.prototype.moveCursorToPosition=function(e){this.selection.moveCursorToPosition(e)},e.prototype.jumpToMatching=function(e,t){var n=this.getCursorPosition(),r=new b(this.session,n.row,n.column),i=r.getCurrentToken(),s=0;i&&i.type.indexOf("tag-name")!==-1&&(i=r.stepBackward());var o=i||r.stepForward();if(!o)return;var u,a=!1,f={},l=n.column-o.start,c,h={")":"(","(":"(","]":"[","[":"[","{":"{","}":"{"};do{if(o.value.match(/[{}()\[\]]/g))for(;l1?f[o.value]++:i.value==="=0;--s)this.$tryReplace(n[s],e)&&r++;return this.selection.setSelectionRange(i),r},e.prototype.$tryReplace=function(e,t){var n=this.session.getTextRange(e);return t=this.$search.replace(n,t),t!==null?(e.end=this.session.replace(e,t),e):null},e.prototype.getLastSearchOptions=function(){return this.$search.getOptions()},e.prototype.find=function(e,t,n){t||(t={}),typeof e=="string"||e instanceof RegExp?t.needle=e:typeof e=="object"&&i.mixin(t,e);var r=this.selection.getRange();t.needle==null&&(e=this.session.getTextRange(r)||this.$search.$options.needle,e||(r=this.session.getWordRange(r.start.row,r.start.column),e=this.session.getTextRange(r)),this.$search.set({needle:e})),this.$search.set(t),t.start||this.$search.set({start:r});var s=this.$search.find(this.session);if(t.preventScroll)return s;if(s)return this.revealRange(s,n),s;t.backwards?r.start=r.end:r.end=r.start,this.selection.setRange(r)},e.prototype.findNext=function(e,t){this.find({skipCurrent:!0,backwards:!1},e,t)},e.prototype.findPrevious=function(e,t){this.find(e,{skipCurrent:!0,backwards:!0},t)},e.prototype.revealRange=function(e,t){this.session.unfold(e),this.selection.setSelectionRange(e);var n=this.renderer.scrollTop;this.renderer.scrollSelectionIntoView(e.start,e.end,.5),t!==!1&&this.renderer.animateScrolling(n)},e.prototype.undo=function(){this.session.getUndoManager().undo(this.session),this.renderer.scrollCursorIntoView(null,.5)},e.prototype.redo=function(){this.session.getUndoManager().redo(this.session),this.renderer.scrollCursorIntoView(null,.5)},e.prototype.destroy=function(){this.$toDestroy&&(this.$toDestroy.forEach(function(e){e.destroy()}),this.$toDestroy=null),this.$mouseHandler&&this.$mouseHandler.destroy(),this.renderer.destroy(),this._signal("destroy",this),this.session&&this.session.destroy(),this._$emitInputEvent&&this._$emitInputEvent.cancel(),this.removeAllListeners()},e.prototype.setAutoScrollEditorIntoView=function(e){if(!e)return;var t,n=this,r=!1;this.$scrollAnchor||(this.$scrollAnchor=document.createElement("div"));var i=this.$scrollAnchor;i.style.cssText="position:absolute",this.container.insertBefore(i,this.container.firstChild);var s=this.on("changeSelection",function(){r=!0}),o=this.renderer.on("beforeRender",function(){r&&(t=n.renderer.container.getBoundingClientRect())}),u=this.renderer.on("afterRender",function(){if(r&&t&&(n.isFocused()||n.searchBox&&n.searchBox.isFocused())){var e=n.renderer,s=e.$cursorLayer.$pixelPos,o=e.layerConfig,u=s.top-o.offset;s.top>=0&&u+t.top<0?r=!0:s.topwindow.innerHeight?r=!1:r=null,r!=null&&(i.style.top=u+"px",i.style.left=s.left+"px",i.style.height=o.lineHeight+"px",i.scrollIntoView(r)),r=t=null}});this.setAutoScrollEditorIntoView=function(e){if(e)return;delete this.setAutoScrollEditorIntoView,this.off("changeSelection",s),this.renderer.off("afterRender",u),this.renderer.off("beforeRender",o)}},e.prototype.$resetCursorStyle=function(){var e=this.$cursorStyle||"ace",t=this.renderer.$cursorLayer;if(!t)return;t.setSmoothBlinking(/smooth/.test(e)),t.isBlinking=!this.$readOnly&&e!="wide",s.setCssClass(t.element,"ace_slim-cursors",/slim/.test(e))},e.prototype.prompt=function(e,t,n){var r=this;y.loadModule("ace/ext/prompt",function(i){i.prompt(r,e,t,n)})},e}();N.$uid=0,N.prototype.curOp=null,N.prototype.prevOp={},N.prototype.$mergeableCommands=["backspace","del","insertstring"],N.prototype.$toggleWordPairs=[["first","last"],["true","false"],["yes","no"],["width","height"],["top","bottom"],["right","left"],["on","off"],["x","y"],["get","set"],["max","min"],["horizontal","vertical"],["show","hide"],["add","remove"],["up","down"],["before","after"],["even","odd"],["in","out"],["inside","outside"],["next","previous"],["increase","decrease"],["attach","detach"],["&&","||"],["==","!="]],i.implement(N.prototype,v),y.defineOptions(N.prototype,"editor",{selectionStyle:{set:function(e){this.onSelectionChange(),this._signal("changeSelectionStyle",{data:e})},initialValue:"line"},highlightActiveLine:{set:function(){this.$updateHighlightActiveLine()},initialValue:!0},highlightSelectedWord:{set:function(e){this.$onSelectionChange()},initialValue:!0},readOnly:{set:function(e){this.textInput.setReadOnly(e),this.$resetCursorStyle()},initialValue:!1},copyWithEmptySelection:{set:function(e){this.textInput.setCopyWithEmptySelection(e)},initialValue:!1},cursorStyle:{set:function(e){this.$resetCursorStyle()},values:["ace","slim","smooth","wide"],initialValue:"ace"},mergeUndoDeltas:{values:[!1,!0,"always"],initialValue:!0},behavioursEnabled:{initialValue:!0},wrapBehavioursEnabled:{initialValue:!0},enableAutoIndent:{initialValue:!0},autoScrollEditorIntoView:{set:function(e){this.setAutoScrollEditorIntoView(e)}},keyboardHandler:{set:function(e){this.setKeyboardHandler(e)},get:function(){return this.$keybindingId},handlesSet:!0},value:{set:function(e){this.session.setValue(e)},get:function(){return this.getValue()},handlesSet:!0,hidden:!0},session:{set:function(e){this.setSession(e)},get:function(){return this.session},handlesSet:!0,hidden:!0},showLineNumbers:{set:function(e){this.renderer.$gutterLayer.setShowLineNumbers(e),this.renderer.$loop.schedule(this.renderer.CHANGE_GUTTER),e&&this.$relativeLineNumbers?C.attach(this):C.detach(this)},initialValue:!0},relativeLineNumbers:{set:function(e){this.$showLineNumbers&&e?C.attach(this):C.detach(this)}},placeholder:{set:function(e){this.$updatePlaceholder||(this.$updatePlaceholder=function(){var e=this.session&&(this.renderer.$composition||this.session.getLength()>1||this.session.getLine(0).length>0);if(e&&this.renderer.placeholderNode)this.renderer.off("afterRender",this.$updatePlaceholder),s.removeCssClass(this.container,"ace_hasPlaceholder"),this.renderer.placeholderNode.remove(),this.renderer.placeholderNode=null;else if(!e&&!this.renderer.placeholderNode){this.renderer.on("afterRender",this.$updatePlaceholder),s.addCssClass(this.container,"ace_hasPlaceholder");var t=s.createElement("div");t.className="ace_placeholder",t.textContent=this.$placeholder||"",this.renderer.placeholderNode=t,this.renderer.content.appendChild(this.renderer.placeholderNode)}else!e&&this.renderer.placeholderNode&&(this.renderer.placeholderNode.textContent=this.$placeholder||"")}.bind(this),this.on("input",this.$updatePlaceholder)),this.$updatePlaceholder()}},enableKeyboardAccessibility:{set:function(e){var t={name:"blurTextInput",description:"Set focus to the editor content div to allow tabbing through the page",bindKey:"Esc",exec:function(e){e.blur(),e.renderer.scroller.focus()},readOnly:!0},n=function(e){if(e.target==this.renderer.scroller&&e.keyCode===T.enter){e.preventDefault();var t=this.getCursorPosition().row;this.isRowVisible(t)||this.scrollToLine(t,!0,!0),this.focus()}},r;e?(this.renderer.enableKeyboardAccessibility=!0,this.renderer.keyboardFocusClassName="ace_keyboard-focus",this.textInput.getElement().setAttribute("tabindex",-1),this.textInput.setNumberOfExtraLines(u.isWin?3:0),this.renderer.scroller.setAttribute("tabindex",0),this.renderer.scroller.setAttribute("role","group"),this.renderer.scroller.setAttribute("aria-roledescription",S("editor")),this.renderer.scroller.classList.add(this.renderer.keyboardFocusClassName),this.renderer.scroller.setAttribute("aria-label",S("Editor content, press Enter to start editing, press Escape to exit")),this.renderer.scroller.addEventListener("keyup",n.bind(this)),this.commands.addCommand(t),this.renderer.$gutter.setAttribute("tabindex",0),this.renderer.$gutter.setAttribute("aria-hidden",!1),this.renderer.$gutter.setAttribute("role","group"),this.renderer.$gutter.setAttribute("aria-roledescription",S("editor")),this.renderer.$gutter.setAttribute("aria-label",S("Editor gutter, press Enter to interact with controls using arrow keys, press Escape to exit")),this.renderer.$gutter.classList.add(this.renderer.keyboardFocusClassName),this.renderer.content.setAttribute("aria-hidden",!0),r||(r=new E(this)),r.addListener()):(this.renderer.enableKeyboardAccessibility=!1,this.textInput.getElement().setAttribute("tabindex",0),this.textInput.setNumberOfExtraLines(0),this.renderer.scroller.setAttribute("tabindex",-1),this.renderer.scroller.removeAttribute("role"),this.renderer.scroller.removeAttribute("aria-roledescription"),this.renderer.scroller.classList.remove(this.renderer.keyboardFocusClassName),this.renderer.scroller.removeAttribute("aria-label"),this.renderer.scroller.removeEventListener("keyup",n.bind(this)),this.commands.removeCommand(t),this.renderer.content.removeAttribute("aria-hidden"),this.renderer.$gutter.setAttribute("tabindex",-1),this.renderer.$gutter.setAttribute("aria-hidden",!0),this.renderer.$gutter.removeAttribute("role"),this.renderer.$gutter.removeAttribute("aria-roledescription"),this.renderer.$gutter.removeAttribute("aria-label"),this.renderer.$gutter.classList.remove(this.renderer.keyboardFocusClassName),r&&r.removeListener())},initialValue:!1},customScrollbar:"renderer",hScrollBarAlwaysVisible:"renderer",vScrollBarAlwaysVisible:"renderer",highlightGutterLine:"renderer",animatedScroll:"renderer",showInvisibles:"renderer",showPrintMargin:"renderer",printMarginColumn:"renderer",printMargin:"renderer",fadeFoldWidgets:"renderer",showFoldWidgets:"renderer",displayIndentGuides:"renderer",highlightIndentGuides:"renderer",showGutter:"renderer",fontSize:"renderer",fontFamily:"renderer",maxLines:"renderer",minLines:"renderer",scrollPastEnd:"renderer",fixedWidthGutter:"renderer",theme:"renderer",hasCssTransforms:"renderer",maxPixelHeight:"renderer",useTextareaForIME:"renderer",useResizeObserver:"renderer",useSvgGutterIcons:"renderer",showFoldedAnnotations:"renderer",scrollSpeed:"$mouseHandler",dragDelay:"$mouseHandler",dragEnabled:"$mouseHandler",focusTimeout:"$mouseHandler",tooltipFollowsMouse:"$mouseHandler",firstLineNumber:"session",overwrite:"session",newLineMode:"session",useWorker:"session",useSoftTabs:"session",navigateWithinSoftTabs:"session",tabSize:"session",wrap:"session",indentedSoftWrap:"session",foldStyle:"session",mode:"session"});var C={getText:function(e,t){return(Math.abs(e.selection.lead.row-t)||t+1+(t<9?"\u00b7":""))+""},getWidth:function(e,t,n){return Math.max(t.toString().length,(n.lastRow+1).toString().length,2)*n.characterWidth},update:function(e,t){t.renderer.$loop.schedule(t.renderer.CHANGE_GUTTER)},attach:function(e){e.renderer.$gutterLayer.$renderer=this,e.on("changeSelection",this.update),this.update(null,e)},detach:function(e){e.renderer.$gutterLayer.$renderer==this&&(e.renderer.$gutterLayer.$renderer=null),e.off("changeSelection",this.update),this.update(null,e)}};t.Editor=N}),define("ace/layer/lines",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";var r=e("../lib/dom"),i=function(){function e(e,t){this.element=e,this.canvasHeight=t||5e5,this.element.style.height=this.canvasHeight*2+"px",this.cells=[],this.cellCache=[],this.$offsetCoefficient=0}return e.prototype.moveContainer=function(e){r.translate(this.element,0,-(e.firstRowScreen*e.lineHeight%this.canvasHeight)-e.offset*this.$offsetCoefficient)},e.prototype.pageChanged=function(e,t){return Math.floor(e.firstRowScreen*e.lineHeight/this.canvasHeight)!==Math.floor(t.firstRowScreen*t.lineHeight/this.canvasHeight)},e.prototype.computeLineTop=function(e,t,n){var r=t.firstRowScreen*t.lineHeight,i=Math.floor(r/this.canvasHeight),s=n.documentToScreenRow(e,0)*t.lineHeight;return s-i*this.canvasHeight},e.prototype.computeLineHeight=function(e,t,n){return t.lineHeight*n.getRowLineCount(e)},e.prototype.getLength=function(){return this.cells.length},e.prototype.get=function(e){return this.cells[e]},e.prototype.shift=function(){this.$cacheCell(this.cells.shift())},e.prototype.pop=function(){this.$cacheCell(this.cells.pop())},e.prototype.push=function(e){if(Array.isArray(e)){this.cells.push.apply(this.cells,e);var t=r.createFragment(this.element);for(var n=0;ns&&(a=i.end.row+1,i=t.getNextFoldLine(a,i),s=i?i.start.row:Infinity);if(a>r){while(this.$lines.getLength()>u+1)this.$lines.pop();break}o=this.$lines.get(++u),o?o.row=a:(o=this.$lines.createCell(a,e,this.session,l),this.$lines.push(o)),this.$renderCell(o,e,i,a),a++}this._signal("afterRender"),this.$updateGutterWidth(e)},e.prototype.$updateGutterWidth=function(e){var t=this.session,n=t.gutterRenderer||this.$renderer,r=t.$firstLineNumber,i=this.$lines.last()?this.$lines.last().text:"";if(this.$fixedWidth||t.$useWrapMode)i=t.getLength()+r-1;var s=n?n.getWidth(t,i,e):i.toString().length*e.characterWidth,o=this.$padding||this.$computePadding();s+=o.left+o.right,s!==this.gutterWidth&&!isNaN(s)&&(this.gutterWidth=s,this.element.parentNode.style.width=this.element.style.width=Math.ceil(this.gutterWidth)+"px",this._signal("changeGutterWidth",s))},e.prototype.$updateCursorRow=function(){if(!this.$highlightGutterLine)return;var e=this.session.selection.getCursor();if(this.$cursorRow===e.row)return;this.$cursorRow=e.row},e.prototype.updateLineHighlight=function(){if(!this.$highlightGutterLine)return;var e=this.session.selection.cursor.row;this.$cursorRow=e;if(this.$cursorCell&&this.$cursorCell.row==e)return;this.$cursorCell&&(this.$cursorCell.element.className=this.$cursorCell.element.className.replace("ace_gutter-active-line ",""));var t=this.$lines.cells;this.$cursorCell=null;for(var n=0;n=this.$cursorRow){if(r.row>this.$cursorRow){var i=this.session.getFoldLine(this.$cursorRow);if(!(n>0&&i&&i.start.row==t[n-1].row))break;r=t[n-1]}r.element.className="ace_gutter-active-line "+r.element.className,this.$cursorCell=r;break}}},e.prototype.scrollLines=function(e){var t=this.config;this.config=e,this.$updateCursorRow();if(this.$lines.pageChanged(t,e))return this.update(e);this.$lines.moveContainer(e);var n=Math.min(e.lastRow+e.gutterOffset,this.session.getLength()-1),r=this.oldLastRow;this.oldLastRow=n;if(!t||r0;i--)this.$lines.shift();if(r>n)for(var i=this.session.getFoldedRowCount(n+1,r);i>0;i--)this.$lines.pop();e.firstRowr&&this.$lines.push(this.$renderLines(e,r+1,n)),this.updateLineHighlight(),this._signal("afterRender"),this.$updateGutterWidth(e)},e.prototype.$renderLines=function(e,t,n){var r=[],i=t,s=this.session.getNextFoldLine(i),o=s?s.start.row:Infinity;for(;;){i>o&&(i=s.end.row+1,s=this.session.getNextFoldLine(i,s),o=s?s.start.row:Infinity);if(i>n)break;var u=this.$lines.createCell(i,e,this.session,l);this.$renderCell(u,e,s,i),r.push(u),i++}return r},e.prototype.$renderCell=function(e,t,n,i){var s=e.element,o=this.session,u=s.childNodes[0],f=s.childNodes[1],l=s.childNodes[2],c=l.firstChild,h=o.$firstLineNumber,p=o.$breakpoints,d=o.$decorations,v=o.gutterRenderer||this.$renderer,m=this.$showFoldWidgets&&o.foldWidgets,g=n?n.start.row:Number.MAX_VALUE,y=t.lineHeight+"px",b=this.$useSvgGutterIcons?"ace_gutter-cell_svg-icons ":"ace_gutter-cell ",w=this.$useSvgGutterIcons?"ace_icon_svg":"ace_icon",E=(v?v.getText(o,i):i+h).toString();this.$highlightGutterLine&&(i==this.$cursorRow||n&&i=g&&this.$cursorRow<=n.end.row)&&(b+="ace_gutter-active-line ",this.$cursorCell!=e&&(this.$cursorCell&&(this.$cursorCell.element.className=this.$cursorCell.element.className.replace("ace_gutter-active-line ","")),this.$cursorCell=e)),p[i]&&(b+=p[i]),d[i]&&(b+=d[i]),this.$annotations[i]&&i!==g&&(b+=this.$annotations[i].className);if(m){var S=m[i];S==null&&(S=m[i]=o.getFoldWidget(i))}if(S){var x="ace_fold-widget ace_"+S,T=S=="start"&&i==g&&in.right-t.right)return"foldWidgets"},e}();f.prototype.$fixedWidth=!1,f.prototype.$highlightGutterLine=!0,f.prototype.$renderer="",f.prototype.$showLineNumbers=!0,f.prototype.$showFoldWidgets=!0,i.implement(f.prototype,o),t.Gutter=f}),define("ace/layer/marker",["require","exports","module","ace/range","ace/lib/dom"],function(e,t,n){"use strict";function o(e,t,n,r){return(e?1:0)|(t?2:0)|(n?4:0)|(r?8:0)}var r=e("../range").Range,i=e("../lib/dom"),s=function(){function e(e){this.element=i.createElement("div"),this.element.className="ace_layer ace_marker-layer",e.appendChild(this.element)}return e.prototype.setPadding=function(e){this.$padding=e},e.prototype.setSession=function(e){this.session=e},e.prototype.setMarkers=function(e){this.markers=e},e.prototype.elt=function(e,t){var n=this.i!=-1&&this.element.childNodes[this.i];n?this.i++:(n=document.createElement("div"),this.element.appendChild(n),this.i=-1),n.style.cssText=t,n.className=e},e.prototype.update=function(e){if(!e)return;this.config=e,this.i=0;var t;for(var n in this.markers){var r=this.markers[n];if(!r.range){r.update(t,this,this.session,e);continue}var i=r.range.clipRows(e.firstRow,e.lastRow);if(i.isEmpty())continue;i=i.toScreenRange(this.session);if(r.renderer){var s=this.$getTop(i.start.row,e),o=this.$padding+i.start.column*e.characterWidth;r.renderer(t,i,o,s,e)}else r.type=="fullLine"?this.drawFullLineMarker(t,i,r.clazz,e):r.type=="screenLine"?this.drawScreenLineMarker(t,i,r.clazz,e):i.isMultiLine()?r.type=="text"?this.drawTextMarker(t,i,r.clazz,e):this.drawMultiLineMarker(t,i,r.clazz,e):this.drawSingleLineMarker(t,i,r.clazz+" ace_start"+" ace_br15",e)}if(this.i!=-1)while(this.ip,l==f),i,l==f?0:1,s)},e.prototype.drawMultiLineMarker=function(e,t,n,r,i){var s=this.$padding,o=r.lineHeight,u=this.$getTop(t.start.row,r),a=s+t.start.column*r.characterWidth;i=i||"";if(this.session.$bidiHandler.isBidiRow(t.start.row)){var f=t.clone();f.end.row=f.start.row,f.end.column=this.session.getLine(f.start.row).length,this.drawBidiSingleLineMarker(e,f,n+" ace_br1 ace_start",r,null,i)}else this.elt(n+" ace_br1 ace_start","height:"+o+"px;"+"right:0;"+"top:"+u+"px;left:"+a+"px;"+(i||""));if(this.session.$bidiHandler.isBidiRow(t.end.row)){var f=t.clone();f.start.row=f.end.row,f.start.column=0,this.drawBidiSingleLineMarker(e,f,n+" ace_br12",r,null,i)}else{u=this.$getTop(t.end.row,r);var l=t.end.column*r.characterWidth;this.elt(n+" ace_br12","height:"+o+"px;"+"width:"+l+"px;"+"top:"+u+"px;"+"left:"+s+"px;"+(i||""))}o=(t.end.row-t.start.row-1)*r.lineHeight;if(o<=0)return;u=this.$getTop(t.start.row+1,r);var c=(t.start.column?1:0)|(t.end.column?0:8);this.elt(n+(c?" ace_br"+c:""),"height:"+o+"px;"+"right:0;"+"top:"+u+"px;"+"left:"+s+"px;"+(i||""))},e.prototype.drawSingleLineMarker=function(e,t,n,r,i,s){if(this.session.$bidiHandler.isBidiRow(t.start.row))return this.drawBidiSingleLineMarker(e,t,n,r,i,s);var o=r.lineHeight,u=(t.end.column+(i||0)-t.start.column)*r.characterWidth,a=this.$getTop(t.start.row,r),f=this.$padding+t.start.column*r.characterWidth;this.elt(n,"height:"+o+"px;"+"width:"+u+"px;"+"top:"+a+"px;"+"left:"+f+"px;"+(s||""))},e.prototype.drawBidiSingleLineMarker=function(e,t,n,r,i,s){var o=r.lineHeight,u=this.$getTop(t.start.row,r),a=this.$padding,f=this.session.$bidiHandler.getSelections(t.start.column,t.end.column);f.forEach(function(e){this.elt(n,"height:"+o+"px;"+"width:"+(e.width+(i||0))+"px;"+"top:"+u+"px;"+"left:"+(a+e.left)+"px;"+(s||""))},this)},e.prototype.drawFullLineMarker=function(e,t,n,r,i){var s=this.$getTop(t.start.row,r),o=r.lineHeight;t.start.row!=t.end.row&&(o+=this.$getTop(t.end.row,r)-s),this.elt(n,"height:"+o+"px;"+"top:"+s+"px;"+"left:0;right:0;"+(i||""))},e.prototype.drawScreenLineMarker=function(e,t,n,r,i){var s=this.$getTop(t.start.row,r),o=r.lineHeight;this.elt(n,"height:"+o+"px;"+"top:"+s+"px;"+"left:0;right:0;"+(i||""))},e}();s.prototype.$padding=0,t.Marker=s}),define("ace/layer/text_util",["require","exports","module"],function(e,t,n){var r=new Set(["text","rparen","lparen"]);t.isTextToken=function(e){return r.has(e)}}),define("ace/layer/text",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/lang","ace/layer/lines","ace/lib/event_emitter","ace/config","ace/layer/text_util"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/dom"),s=e("../lib/lang"),o=e("./lines").Lines,u=e("../lib/event_emitter").EventEmitter,a=e("../config").nls,f=e("./text_util").isTextToken,l=function(){function e(e){this.dom=i,this.element=this.dom.createElement("div"),this.element.className="ace_layer ace_text-layer",e.appendChild(this.element),this.$updateEolChar=this.$updateEolChar.bind(this),this.$lines=new o(this.element)}return e.prototype.$updateEolChar=function(){var e=this.session.doc,t=e.getNewLineCharacter()=="\n"&&e.getNewLineMode()!="windows",n=t?this.EOL_CHAR_LF:this.EOL_CHAR_CRLF;if(this.EOL_CHAR!=n)return this.EOL_CHAR=n,!0},e.prototype.setPadding=function(e){this.$padding=e,this.element.style.margin="0 "+e+"px"},e.prototype.getLineHeight=function(){return this.$fontMetrics.$characterSize.height||0},e.prototype.getCharacterWidth=function(){return this.$fontMetrics.$characterSize.width||0},e.prototype.$setFontMetrics=function(e){this.$fontMetrics=e,this.$fontMetrics.on("changeCharacterSize",function(e){this._signal("changeCharacterSize",e)}.bind(this)),this.$pollSizeChanges()},e.prototype.checkForSizeChanges=function(){this.$fontMetrics.checkForSizeChanges()},e.prototype.$pollSizeChanges=function(){return this.$pollSizeChangesTimer=this.$fontMetrics.$pollSizeChanges()},e.prototype.setSession=function(e){this.session=e,e&&this.$computeTabString()},e.prototype.setShowInvisibles=function(e){return this.showInvisibles==e?!1:(this.showInvisibles=e,typeof e=="string"?(this.showSpaces=/tab/i.test(e),this.showTabs=/space/i.test(e),this.showEOL=/eol/i.test(e)):this.showSpaces=this.showTabs=this.showEOL=e,this.$computeTabString(),!0)},e.prototype.setDisplayIndentGuides=function(e){return this.displayIndentGuides==e?!1:(this.displayIndentGuides=e,this.$computeTabString(),!0)},e.prototype.setHighlightIndentGuides=function(e){return this.$highlightIndentGuides===e?!1:(this.$highlightIndentGuides=e,e)},e.prototype.$computeTabString=function(){var e=this.session.getTabSize();this.tabSize=e;var t=this.$tabStrings=[0];for(var n=1;nl&&(u=a.end.row+1,a=this.session.getNextFoldLine(u,a),l=a?a.start.row:Infinity);if(u>i)break;var c=s[o++];if(c){this.dom.removeChildren(c),this.$renderLine(c,u,u==l?a:!1),f&&(c.style.top=this.$lines.computeLineTop(u,e,this.session)+"px");var h=e.lineHeight*this.session.getRowLength(u)+"px";c.style.height!=h&&(f=!0,c.style.height=h)}u++}if(f)while(o0;i--)this.$lines.shift();if(t.lastRow>e.lastRow)for(var i=this.session.getFoldedRowCount(e.lastRow+1,t.lastRow);i>0;i--)this.$lines.pop();e.firstRowt.lastRow&&this.$lines.push(this.$renderLinesFragment(e,t.lastRow+1,e.lastRow)),this.$highlightIndentGuide()},e.prototype.$renderLinesFragment=function(e,t,n){var r=[],s=t,o=this.session.getNextFoldLine(s),u=o?o.start.row:Infinity;for(;;){s>u&&(s=o.end.row+1,o=this.session.getNextFoldLine(s,o),u=o?o.start.row:Infinity);if(s>n)break;var a=this.$lines.createCell(s,e,this.session),f=a.element;this.dom.removeChildren(f),i.setStyle(f.style,"height",this.$lines.computeLineHeight(s,e,this.session)+"px"),i.setStyle(f.style,"top",this.$lines.computeLineTop(s,e,this.session)+"px"),this.$renderLine(f,s,s==u?o:!1),this.$useLineGroups()?f.className="ace_line_group":f.className="ace_line",r.push(a),s++}return r},e.prototype.update=function(e){this.$lines.moveContainer(e),this.config=e;var t=e.firstRow,n=e.lastRow,r=this.$lines;while(r.getLength())r.pop();r.push(this.$renderLinesFragment(e,t,n))},e.prototype.$renderToken=function(e,t,n,r){var i=this,o=/(\t)|( +)|([\x00-\x1f\x80-\xa0\xad\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\uFEFF\uFFF9-\uFFFC\u2066\u2067\u2068\u202A\u202B\u202D\u202E\u202C\u2069]+)|(\u3000)|([\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3001-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]|[\uD800-\uDBFF][\uDC00-\uDFFF])/g,u=this.dom.createFragment(this.element),l,c=0;while(l=o.exec(r)){var h=l[1],p=l[2],d=l[3],v=l[4],m=l[5];if(!i.showSpaces&&p)continue;var g=c!=l.index?r.slice(c,l.index):"";c=l.index+l[0].length,g&&u.appendChild(this.dom.createTextNode(g,this.element));if(h){var y=i.session.getScreenTabSize(t+l.index);u.appendChild(i.$tabStrings[y].cloneNode(!0)),t+=y-1}else if(p)if(i.showSpaces){var b=this.dom.createElement("span");b.className="ace_invisible ace_invisible_space",b.textContent=s.stringRepeat(i.SPACE_CHAR,p.length),u.appendChild(b)}else u.appendChild(this.dom.createTextNode(p,this.element));else if(d){var b=this.dom.createElement("span");b.className="ace_invisible ace_invisible_space ace_invalid",b.textContent=s.stringRepeat(i.SPACE_CHAR,d.length),u.appendChild(b)}else if(v){t+=1;var b=this.dom.createElement("span");b.style.width=i.config.characterWidth*2+"px",b.className=i.showSpaces?"ace_cjk ace_invisible ace_invisible_space":"ace_cjk",b.textContent=i.showSpaces?i.SPACE_CHAR:v,u.appendChild(b)}else if(m){t+=1;var b=this.dom.createElement("span");b.style.width=i.config.characterWidth*2+"px",b.className="ace_cjk",b.textContent=m,u.appendChild(b)}}u.appendChild(this.dom.createTextNode(c?r.slice(c):r,this.element));if(!f(n.type)){var w="ace_"+n.type.replace(/\./g," ace_"),b=this.dom.createElement("span");n.type=="fold"&&(b.style.width=n.value.length*this.config.characterWidth+"px",b.setAttribute("title",a("Unfold code"))),b.className=w,b.appendChild(u),e.appendChild(b)}else e.appendChild(u);return t+r.length},e.prototype.renderIndentGuide=function(e,t,n){var r=t.search(this.$indentGuideRe);if(r<=0||r>=n)return t;if(t[0]==" "){r-=r%this.tabSize;var i=r/this.tabSize;for(var s=0;ss[o].start.row?this.$highlightIndentGuideMarker.dir=-1:this.$highlightIndentGuideMarker.dir=1;break}}if(!this.$highlightIndentGuideMarker.end&&e[t.row]!==""&&t.column===e[t.row].length){this.$highlightIndentGuideMarker.dir=1;for(var o=t.row+1;o0)for(var i=0;i=this.$highlightIndentGuideMarker.start+1){if(r.row>=this.$highlightIndentGuideMarker.end)break;this.$setIndentGuideActive(r,t)}}else for(var n=e.length-1;n>=0;n--){var r=e[n];if(this.$highlightIndentGuideMarker.end&&r.row=o)u=this.$renderToken(a,u,l,c.substring(0,o-r)),c=c.substring(o-r),r=o,a=this.$createLineElement(),e.appendChild(a),a.appendChild(this.dom.createTextNode(s.stringRepeat("\u00a0",n.indent),this.element)),i++,u=0,o=n[i]||Number.MAX_VALUE;c.length!=0&&(r+=c.length,u=this.$renderToken(a,u,l,c))}}n[n.length-1]>this.MAX_LINE_LENGTH&&this.$renderOverflowMessage(a,u,null,"",!0)},e.prototype.$renderSimpleLine=function(e,t){var n=0;for(var r=0;rthis.MAX_LINE_LENGTH)return this.$renderOverflowMessage(e,n,i,s);n=this.$renderToken(e,n,i,s)}},e.prototype.$renderOverflowMessage=function(e,t,n,r,i){n&&this.$renderToken(e,t,n,r.slice(0,this.MAX_LINE_LENGTH-t));var s=this.dom.createElement("span");s.className="ace_inline_button ace_keyword ace_toggle_wrap",s.textContent=i?"":"",e.appendChild(s)},e.prototype.$renderLine=function(e,t,n){!n&&n!=0&&(n=this.session.getFoldLine(t));if(n)var r=this.$getFoldLineTokens(t,n);else var r=this.session.getTokens(t);var i=e;if(r.length){var s=this.session.getRowSplitData(t);if(s&&s.length){this.$renderWrappedLine(e,r,s);var i=e.lastChild}else{var i=e;this.$useLineGroups()&&(i=this.$createLineElement(),e.appendChild(i)),this.$renderSimpleLine(i,r)}}else this.$useLineGroups()&&(i=this.$createLineElement(),e.appendChild(i));if(this.showEOL&&i){n&&(t=n.end.row);var o=this.dom.createElement("span");o.className="ace_invisible ace_invisible_eol",o.textContent=t==this.session.getLength()-1?this.EOF_CHAR:this.EOL_CHAR,i.appendChild(o)}},e.prototype.$getFoldLineTokens=function(e,t){function i(e,t,n){var i=0,s=0;while(s+e[i].value.lengthn-t&&(o=o.substring(0,n-t)),r.push({type:e[i].type,value:o}),s=t+o.length,i+=1}while(sn?r.push({type:e[i].type,value:o.substring(0,n-s)}):r.push(e[i]),s+=o.length,i+=1}}var n=this.session,r=[],s=n.getTokens(e);return t.walk(function(e,t,o,u,a){e!=null?r.push({type:"fold",value:e}):(a&&(s=n.getTokens(t)),s.length&&i(s,u,o))},t.end.row,this.session.getLine(t.end.row).length),r},e.prototype.$useLineGroups=function(){return this.session.getUseWrapMode()},e}();l.prototype.EOF_CHAR="\u00b6",l.prototype.EOL_CHAR_LF="\u00ac",l.prototype.EOL_CHAR_CRLF="\u00a4",l.prototype.EOL_CHAR=l.prototype.EOL_CHAR_LF,l.prototype.TAB_CHAR="\u2014",l.prototype.SPACE_CHAR="\u00b7",l.prototype.$padding=0,l.prototype.MAX_LINE_LENGTH=1e4,l.prototype.showInvisibles=!1,l.prototype.showSpaces=!1,l.prototype.showTabs=!1,l.prototype.showEOL=!1,l.prototype.displayIndentGuides=!0,l.prototype.$highlightIndentGuides=!0,l.prototype.$tabStrings=[],l.prototype.destroy={},l.prototype.onChangeTabSize=l.prototype.$computeTabString,r.implement(l.prototype,u),t.Text=l}),define("ace/layer/cursor",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";var r=e("../lib/dom"),i=function(){function e(e){this.element=r.createElement("div"),this.element.className="ace_layer ace_cursor-layer",e.appendChild(this.element),this.isVisible=!1,this.isBlinking=!0,this.blinkInterval=1e3,this.smoothBlinking=!1,this.cursors=[],this.cursor=this.addCursor(),r.addCssClass(this.element,"ace_hidden-cursors"),this.$updateCursors=this.$updateOpacity.bind(this)}return e.prototype.$updateOpacity=function(e){var t=this.cursors;for(var n=t.length;n--;)r.setStyle(t[n].style,"opacity",e?"":"0")},e.prototype.$startCssAnimation=function(){var e=this.cursors;for(var t=e.length;t--;)e[t].style.animationDuration=this.blinkInterval+"ms";this.$isAnimating=!0,setTimeout(function(){this.$isAnimating&&r.addCssClass(this.element,"ace_animate-blinking")}.bind(this))},e.prototype.$stopCssAnimation=function(){this.$isAnimating=!1,r.removeCssClass(this.element,"ace_animate-blinking")},e.prototype.setPadding=function(e){this.$padding=e},e.prototype.setSession=function(e){this.session=e},e.prototype.setBlinking=function(e){e!=this.isBlinking&&(this.isBlinking=e,this.restartTimer())},e.prototype.setBlinkInterval=function(e){e!=this.blinkInterval&&(this.blinkInterval=e,this.restartTimer())},e.prototype.setSmoothBlinking=function(e){e!=this.smoothBlinking&&(this.smoothBlinking=e,r.setCssClass(this.element,"ace_smooth-blinking",e),this.$updateCursors(!0),this.restartTimer())},e.prototype.addCursor=function(){var e=r.createElement("div");return e.className="ace_cursor",this.element.appendChild(e),this.cursors.push(e),e},e.prototype.removeCursor=function(){if(this.cursors.length>1){var e=this.cursors.pop();return e.parentNode.removeChild(e),e}},e.prototype.hideCursor=function(){this.isVisible=!1,r.addCssClass(this.element,"ace_hidden-cursors"),this.restartTimer()},e.prototype.showCursor=function(){this.isVisible=!0,r.removeCssClass(this.element,"ace_hidden-cursors"),this.restartTimer()},e.prototype.restartTimer=function(){var e=this.$updateCursors;clearInterval(this.intervalId),clearTimeout(this.timeoutId),this.$stopCssAnimation(),this.smoothBlinking&&(this.$isSmoothBlinking=!1,r.removeCssClass(this.element,"ace_smooth-blinking")),e(!0);if(!this.isBlinking||!this.blinkInterval||!this.isVisible){this.$stopCssAnimation();return}this.smoothBlinking&&(this.$isSmoothBlinking=!0,setTimeout(function(){this.$isSmoothBlinking&&r.addCssClass(this.element,"ace_smooth-blinking")}.bind(this)));if(r.HAS_CSS_ANIMATION)this.$startCssAnimation();else{var t=function(){this.timeoutId=setTimeout(function(){e(!1)},.6*this.blinkInterval)}.bind(this);this.intervalId=setInterval(function(){e(!0),t()},this.blinkInterval),t()}},e.prototype.getPixelPosition=function(e,t){if(!this.config||!this.session)return{left:0,top:0};e||(e=this.session.selection.getCursor());var n=this.session.documentToScreenPosition(e),r=this.$padding+(this.session.$bidiHandler.isBidiRow(n.row,e.row)?this.session.$bidiHandler.getPosLeft(n.column):n.column*this.config.characterWidth),i=(n.row-(t?this.config.firstRowScreen:0))*this.config.lineHeight;return{left:r,top:i}},e.prototype.isCursorInView=function(e,t){return e.top>=0&&e.tope.height+e.offset||o.top<0)&&n>1)continue;var u=this.cursors[i++]||this.addCursor(),a=u.style;this.drawCursor?this.drawCursor(u,o,e,t[n],this.session):this.isCursorInView(o,e)?(r.setStyle(a,"display","block"),r.translate(u,o.left,o.top),r.setStyle(a,"width",Math.round(e.characterWidth)+"px"),r.setStyle(a,"height",e.lineHeight+"px")):r.setStyle(a,"display","none")}while(this.cursors.length>i)this.removeCursor();var f=this.session.getOverwrite();this.$setOverwrite(f),this.$pixelPos=o,this.restartTimer()},e.prototype.$setOverwrite=function(e){e!=this.overwrite&&(this.overwrite=e,e?r.addCssClass(this.element,"ace_overwrite-cursors"):r.removeCssClass(this.element,"ace_overwrite-cursors"))},e.prototype.destroy=function(){clearInterval(this.intervalId),clearTimeout(this.timeoutId)},e}();i.prototype.$padding=0,i.prototype.drawCursor=null,t.Cursor=i}),define("ace/scrollbar",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/event","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=this&&this.__extends||function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},e(t,n)};return function(t,n){function r(){this.constructor=t}if(typeof n!="function"&&n!==null)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");e(t,n),t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),i=e("./lib/oop"),s=e("./lib/dom"),o=e("./lib/event"),u=e("./lib/event_emitter").EventEmitter,a=32768,f=function(){function e(e,t){this.element=s.createElement("div"),this.element.className="ace_scrollbar ace_scrollbar"+t,this.inner=s.createElement("div"),this.inner.className="ace_scrollbar-inner",this.inner.textContent="\u00a0",this.element.appendChild(this.inner),e.appendChild(this.element),this.setVisible(!1),this.skipEvent=!1,o.addListener(this.element,"scroll",this.onScroll.bind(this)),o.addListener(this.element,"mousedown",o.preventDefault)}return e.prototype.setVisible=function(e){this.element.style.display=e?"":"none",this.isVisible=e,this.coeff=1},e}();i.implement(f.prototype,u);var l=function(e){function t(t,n){var r=e.call(this,t,"-v")||this;return r.scrollTop=0,r.scrollHeight=0,n.$scrollbarWidth=r.width=s.scrollbarWidth(t.ownerDocument),r.inner.style.width=r.element.style.width=(r.width||15)+5+"px",r.$minWidth=0,r}return r(t,e),t.prototype.onScroll=function(){if(!this.skipEvent){this.scrollTop=this.element.scrollTop;if(this.coeff!=1){var e=this.element.clientHeight/this.scrollHeight;this.scrollTop=this.scrollTop*(1-e)/(this.coeff-e)}this._emit("scroll",{data:this.scrollTop})}this.skipEvent=!1},t.prototype.getWidth=function(){return Math.max(this.isVisible?this.width:0,this.$minWidth||0)},t.prototype.setHeight=function(e){this.element.style.height=e+"px"},t.prototype.setScrollHeight=function(e){this.scrollHeight=e,e>a?(this.coeff=a/e,e=a):this.coeff!=1&&(this.coeff=1),this.inner.style.height=e+"px"},t.prototype.setScrollTop=function(e){this.scrollTop!=e&&(this.skipEvent=!0,this.scrollTop=e,this.element.scrollTop=e*this.coeff)},t}(f);l.prototype.setInnerHeight=l.prototype.setScrollHeight;var c=function(e){function t(t,n){var r=e.call(this,t,"-h")||this;return r.scrollLeft=0,r.height=n.$scrollbarWidth,r.inner.style.height=r.element.style.height=(r.height||15)+5+"px",r}return r(t,e),t.prototype.onScroll=function(){this.skipEvent||(this.scrollLeft=this.element.scrollLeft,this._emit("scroll",{data:this.scrollLeft})),this.skipEvent=!1},t.prototype.getHeight=function(){return this.isVisible?this.height:0},t.prototype.setWidth=function(e){this.element.style.width=e+"px"},t.prototype.setInnerWidth=function(e){this.inner.style.width=e+"px"},t.prototype.setScrollWidth=function(e){this.inner.style.width=e+"px"},t.prototype.setScrollLeft=function(e){this.scrollLeft!=e&&(this.skipEvent=!0,this.scrollLeft=this.element.scrollLeft=e)},t}(f);t.ScrollBar=l,t.ScrollBarV=l,t.ScrollBarH=c,t.VScrollBar=l,t.HScrollBar=c}),define("ace/scrollbar_custom",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/event","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=this&&this.__extends||function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},e(t,n)};return function(t,n){function r(){this.constructor=t}if(typeof n!="function"&&n!==null)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");e(t,n),t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),i=e("./lib/oop"),s=e("./lib/dom"),o=e("./lib/event"),u=e("./lib/event_emitter").EventEmitter;s.importCssString(".ace_editor>.ace_sb-v div, .ace_editor>.ace_sb-h div{\n position: absolute;\n background: rgba(128, 128, 128, 0.6);\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n border: 1px solid #bbb;\n border-radius: 2px;\n z-index: 8;\n}\n.ace_editor>.ace_sb-v, .ace_editor>.ace_sb-h {\n position: absolute;\n z-index: 6;\n background: none;\n overflow: hidden!important;\n}\n.ace_editor>.ace_sb-v {\n z-index: 6;\n right: 0;\n top: 0;\n width: 12px;\n}\n.ace_editor>.ace_sb-v div {\n z-index: 8;\n right: 0;\n width: 100%;\n}\n.ace_editor>.ace_sb-h {\n bottom: 0;\n left: 0;\n height: 12px;\n}\n.ace_editor>.ace_sb-h div {\n bottom: 0;\n height: 100%;\n}\n.ace_editor>.ace_sb_grabbed {\n z-index: 8;\n background: #000;\n}","ace_scrollbar.css",!1);var a=function(){function e(e,t){this.element=s.createElement("div"),this.element.className="ace_sb"+t,this.inner=s.createElement("div"),this.inner.className="",this.element.appendChild(this.inner),this.VScrollWidth=12,this.HScrollHeight=12,e.appendChild(this.element),this.setVisible(!1),this.skipEvent=!1,o.addMultiMouseDownListener(this.element,[500,300,300],this,"onMouseDown")}return e.prototype.setVisible=function(e){this.element.style.display=e?"":"none",this.isVisible=e,this.coeff=1},e}();i.implement(a.prototype,u);var f=function(e){function t(t,n){var r=e.call(this,t,"-v")||this;return r.scrollTop=0,r.scrollHeight=0,r.parent=t,r.width=r.VScrollWidth,r.renderer=n,r.inner.style.width=r.element.style.width=(r.width||15)+"px",r.$minWidth=0,r}return r(t,e),t.prototype.onMouseDown=function(e,t){if(e!=="mousedown")return;if(o.getButton(t)!==0||t.detail===2)return;if(t.target===this.inner){var n=this,r=t.clientY,i=function(e){r=e.clientY},s=function(){clearInterval(l)},u=t.clientY,a=this.thumbTop,f=function(){if(r===undefined)return;var e=n.scrollTopFromThumbTop(a+r-u);if(e===n.scrollTop)return;n._emit("scroll",{data:e})};o.capture(this.inner,i,s);var l=setInterval(f,20);return o.preventDefault(t)}var c=t.clientY-this.element.getBoundingClientRect().top-this.thumbHeight/2;return this._emit("scroll",{data:this.scrollTopFromThumbTop(c)}),o.preventDefault(t)},t.prototype.getHeight=function(){return this.height},t.prototype.scrollTopFromThumbTop=function(e){var t=e*(this.pageHeight-this.viewHeight)/(this.slideHeight-this.thumbHeight);return t>>=0,t<0?t=0:t>this.pageHeight-this.viewHeight&&(t=this.pageHeight-this.viewHeight),t},t.prototype.getWidth=function(){return Math.max(this.isVisible?this.width:0,this.$minWidth||0)},t.prototype.setHeight=function(e){this.height=Math.max(0,e),this.slideHeight=this.height,this.viewHeight=this.height,this.setScrollHeight(this.pageHeight,!0)},t.prototype.setScrollHeight=function(e,t){if(this.pageHeight===e&&!t)return;this.pageHeight=e,this.thumbHeight=this.slideHeight*this.viewHeight/this.pageHeight,this.thumbHeight>this.slideHeight&&(this.thumbHeight=this.slideHeight),this.thumbHeight<15&&(this.thumbHeight=15),this.inner.style.height=this.thumbHeight+"px",this.scrollTop>this.pageHeight-this.viewHeight&&(this.scrollTop=this.pageHeight-this.viewHeight,this.scrollTop<0&&(this.scrollTop=0),this._emit("scroll",{data:this.scrollTop}))},t.prototype.setScrollTop=function(e){this.scrollTop=e,e<0&&(e=0),this.thumbTop=e*(this.slideHeight-this.thumbHeight)/(this.pageHeight-this.viewHeight),this.inner.style.top=this.thumbTop+"px"},t}(a);f.prototype.setInnerHeight=f.prototype.setScrollHeight;var l=function(e){function t(t,n){var r=e.call(this,t,"-h")||this;return r.scrollLeft=0,r.scrollWidth=0,r.height=r.HScrollHeight,r.inner.style.height=r.element.style.height=(r.height||12)+"px",r.renderer=n,r}return r(t,e),t.prototype.onMouseDown=function(e,t){if(e!=="mousedown")return;if(o.getButton(t)!==0||t.detail===2)return;if(t.target===this.inner){var n=this,r=t.clientX,i=function(e){r=e.clientX},s=function(){clearInterval(l)},u=t.clientX,a=this.thumbLeft,f=function(){if(r===undefined)return;var e=n.scrollLeftFromThumbLeft(a+r-u);if(e===n.scrollLeft)return;n._emit("scroll",{data:e})};o.capture(this.inner,i,s);var l=setInterval(f,20);return o.preventDefault(t)}var c=t.clientX-this.element.getBoundingClientRect().left-this.thumbWidth/2;return this._emit("scroll",{data:this.scrollLeftFromThumbLeft(c)}),o.preventDefault(t)},t.prototype.getHeight=function(){return this.isVisible?this.height:0},t.prototype.scrollLeftFromThumbLeft=function(e){var t=e*(this.pageWidth-this.viewWidth)/(this.slideWidth-this.thumbWidth);return t>>=0,t<0?t=0:t>this.pageWidth-this.viewWidth&&(t=this.pageWidth-this.viewWidth),t},t.prototype.setWidth=function(e){this.width=Math.max(0,e),this.element.style.width=this.width+"px",this.slideWidth=this.width,this.viewWidth=this.width,this.setScrollWidth(this.pageWidth,!0)},t.prototype.setScrollWidth=function(e,t){if(this.pageWidth===e&&!t)return;this.pageWidth=e,this.thumbWidth=this.slideWidth*this.viewWidth/this.pageWidth,this.thumbWidth>this.slideWidth&&(this.thumbWidth=this.slideWidth),this.thumbWidth<15&&(this.thumbWidth=15),this.inner.style.width=this.thumbWidth+"px",this.scrollLeft>this.pageWidth-this.viewWidth&&(this.scrollLeft=this.pageWidth-this.viewWidth,this.scrollLeft<0&&(this.scrollLeft=0),this._emit("scroll",{data:this.scrollLeft}))},t.prototype.setScrollLeft=function(e){this.scrollLeft=e,e<0&&(e=0),this.thumbLeft=e*(this.slideWidth-this.thumbWidth)/(this.pageWidth-this.viewWidth),this.inner.style.left=this.thumbLeft+"px"},t}(a);l.prototype.setInnerWidth=l.prototype.setScrollWidth,t.ScrollBar=f,t.ScrollBarV=f,t.ScrollBarH=l,t.VScrollBar=f,t.HScrollBar=l}),define("ace/renderloop",["require","exports","module","ace/lib/event"],function(e,t,n){"use strict";var r=e("./lib/event"),i=function(){function e(e,t){this.onRender=e,this.pending=!1,this.changes=0,this.$recursionLimit=2,this.window=t||window;var n=this;this._flush=function(e){n.pending=!1;var t=n.changes;t&&(r.blockIdle(100),n.changes=0,n.onRender(t));if(n.changes){if(n.$recursionLimit--<0)return;n.schedule()}else n.$recursionLimit=2}}return e.prototype.schedule=function(e){this.changes=this.changes|e,this.changes&&!this.pending&&(r.nextFrame(this._flush),this.pending=!0)},e.prototype.clear=function(e){var t=this.changes;return this.changes=0,t},e}();t.RenderLoop=i}),define("ace/layer/font_metrics",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/lang","ace/lib/event","ace/lib/useragent","ace/lib/event_emitter"],function(e,t,n){var r=e("../lib/oop"),i=e("../lib/dom"),s=e("../lib/lang"),o=e("../lib/event"),u=e("../lib/useragent"),a=e("../lib/event_emitter").EventEmitter,f=512,l=typeof ResizeObserver=="function",c=200,h=function(){function e(e){this.el=i.createElement("div"),this.$setMeasureNodeStyles(this.el.style,!0),this.$main=i.createElement("div"),this.$setMeasureNodeStyles(this.$main.style),this.$measureNode=i.createElement("div"),this.$setMeasureNodeStyles(this.$measureNode.style),this.el.appendChild(this.$main),this.el.appendChild(this.$measureNode),e.appendChild(this.el),this.$measureNode.textContent=s.stringRepeat("X",f),this.$characterSize={width:0,height:0},l?this.$addObserver():this.checkForSizeChanges()}return e.prototype.$setMeasureNodeStyles=function(e,t){e.width=e.height="auto",e.left=e.top="0px",e.visibility="hidden",e.position="absolute",e.whiteSpace="pre",u.isIE<8?e["font-family"]="inherit":e.font="inherit",e.overflow=t?"hidden":"visible"},e.prototype.checkForSizeChanges=function(e){e===undefined&&(e=this.$measureSizes());if(e&&(this.$characterSize.width!==e.width||this.$characterSize.height!==e.height)){this.$measureNode.style.fontWeight="bold";var t=this.$measureSizes();this.$measureNode.style.fontWeight="",this.$characterSize=e,this.charSizes=Object.create(null),this.allowBoldFonts=t&&t.width===e.width&&t.height===e.height,this._emit("changeCharacterSize",{data:e})}},e.prototype.$addObserver=function(){var e=this;this.$observer=new window.ResizeObserver(function(t){e.checkForSizeChanges()}),this.$observer.observe(this.$measureNode)},e.prototype.$pollSizeChanges=function(){if(this.$pollSizeChangesTimer||this.$observer)return this.$pollSizeChangesTimer;var e=this;return this.$pollSizeChangesTimer=o.onIdle(function t(){e.checkForSizeChanges(),o.onIdle(t,500)},500)},e.prototype.setPolling=function(e){e?this.$pollSizeChanges():this.$pollSizeChangesTimer&&(clearInterval(this.$pollSizeChangesTimer),this.$pollSizeChangesTimer=0)},e.prototype.$measureSizes=function(e){var t={height:(e||this.$measureNode).clientHeight,width:(e||this.$measureNode).clientWidth/f};return t.width===0||t.height===0?null:t},e.prototype.$measureCharWidth=function(e){this.$main.textContent=s.stringRepeat(e,f);var t=this.$main.getBoundingClientRect();return t.width/f},e.prototype.getCharacterWidth=function(e){var t=this.charSizes[e];return t===undefined&&(t=this.charSizes[e]=this.$measureCharWidth(e)/this.$characterSize.width),t},e.prototype.destroy=function(){clearInterval(this.$pollSizeChangesTimer),this.$observer&&this.$observer.disconnect(),this.el&&this.el.parentNode&&this.el.parentNode.removeChild(this.el)},e.prototype.$getZoom=function(e){return!e||!e.parentElement?1:(window.getComputedStyle(e).zoom||1)*this.$getZoom(e.parentElement)},e.prototype.$initTransformMeasureNodes=function(){var e=function(e,t){return["div",{style:"position: absolute;top:"+e+"px;left:"+t+"px;"}]};this.els=i.buildDom([e(0,0),e(c,0),e(0,c),e(c,c)],this.el)},e.prototype.transformCoordinates=function(e,t){function r(e,t,n){var r=e[1]*t[0]-e[0]*t[1];return[(-t[1]*n[0]+t[0]*n[1])/r,(+e[1]*n[0]-e[0]*n[1])/r]}function i(e,t){return[e[0]-t[0],e[1]-t[1]]}function s(e,t){return[e[0]+t[0],e[1]+t[1]]}function o(e,t){return[e*t[0],e*t[1]]}function u(e){var t=e.getBoundingClientRect();return[t.left,t.top]}if(e){var n=this.$getZoom(this.el);e=o(1/n,e)}this.els||this.$initTransformMeasureNodes();var a=u(this.els[0]),f=u(this.els[1]),l=u(this.els[2]),h=u(this.els[3]),p=r(i(h,f),i(h,l),i(s(f,l),s(h,a))),d=o(1+p[0],i(f,a)),v=o(1+p[1],i(l,a));if(t){var m=t,g=p[0]*m[0]/c+p[1]*m[1]/c+1,y=s(o(m[0],d),o(m[1],v));return s(o(1/g/c,y),a)}var b=i(e,a),w=r(i(d,o(p[0],b)),i(v,o(p[1],b)),b);return o(c,w)},e}();h.prototype.$characterSize={width:0,height:0},r.implement(h.prototype,a),t.FontMetrics=h}),define("ace/css/editor-css",["require","exports","module"],function(e,t,n){n.exports='\n.ace_br1 {border-top-left-radius : 3px;}\n.ace_br2 {border-top-right-radius : 3px;}\n.ace_br3 {border-top-left-radius : 3px; border-top-right-radius: 3px;}\n.ace_br4 {border-bottom-right-radius: 3px;}\n.ace_br5 {border-top-left-radius : 3px; border-bottom-right-radius: 3px;}\n.ace_br6 {border-top-right-radius : 3px; border-bottom-right-radius: 3px;}\n.ace_br7 {border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px;}\n.ace_br8 {border-bottom-left-radius : 3px;}\n.ace_br9 {border-top-left-radius : 3px; border-bottom-left-radius: 3px;}\n.ace_br10{border-top-right-radius : 3px; border-bottom-left-radius: 3px;}\n.ace_br11{border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-left-radius: 3px;}\n.ace_br12{border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}\n.ace_br13{border-top-left-radius : 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}\n.ace_br14{border-top-right-radius : 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}\n.ace_br15{border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}\n\n\n.ace_editor {\n position: relative;\n overflow: hidden;\n padding: 0;\n font: 12px/normal \'Monaco\', \'Menlo\', \'Ubuntu Mono\', \'Consolas\', \'Source Code Pro\', \'source-code-pro\', monospace;\n direction: ltr;\n text-align: left;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\n.ace_scroller {\n position: absolute;\n overflow: hidden;\n top: 0;\n bottom: 0;\n background-color: inherit;\n -ms-user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n user-select: none;\n cursor: text;\n}\n\n.ace_content {\n position: absolute;\n box-sizing: border-box;\n min-width: 100%;\n contain: style size layout;\n font-variant-ligatures: no-common-ligatures;\n}\n\n.ace_keyboard-focus:focus {\n box-shadow: inset 0 0 0 2px #5E9ED6;\n outline: none;\n}\n\n.ace_dragging .ace_scroller:before{\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n content: \'\';\n background: rgba(250, 250, 250, 0.01);\n z-index: 1000;\n}\n.ace_dragging.ace_dark .ace_scroller:before{\n background: rgba(0, 0, 0, 0.01);\n}\n\n.ace_gutter {\n position: absolute;\n overflow : hidden;\n width: auto;\n top: 0;\n bottom: 0;\n left: 0;\n cursor: default;\n z-index: 4;\n -ms-user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n user-select: none;\n contain: style size layout;\n}\n\n.ace_gutter-active-line {\n position: absolute;\n left: 0;\n right: 0;\n}\n\n.ace_scroller.ace_scroll-left:after {\n content: "";\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n box-shadow: 17px 0 16px -16px rgba(0, 0, 0, 0.4) inset;\n pointer-events: none;\n}\n\n.ace_gutter-cell, .ace_gutter-cell_svg-icons {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n padding-left: 19px;\n padding-right: 6px;\n background-repeat: no-repeat;\n}\n\n.ace_gutter-cell_svg-icons .ace_gutter_annotation {\n margin-left: -14px;\n float: left;\n}\n\n.ace_gutter-cell .ace_gutter_annotation {\n margin-left: -19px;\n float: left;\n}\n\n.ace_gutter-cell.ace_error, .ace_icon.ace_error, .ace_icon.ace_error_fold {\n background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABOFBMVEX/////////QRswFAb/Ui4wFAYwFAYwFAaWGAfDRymzOSH/PxswFAb/SiUwFAYwFAbUPRvjQiDllog5HhHdRybsTi3/Tyv9Tir+Syj/UC3////XurebMBIwFAb/RSHbPx/gUzfdwL3kzMivKBAwFAbbvbnhPx66NhowFAYwFAaZJg8wFAaxKBDZurf/RB6mMxb/SCMwFAYwFAbxQB3+RB4wFAb/Qhy4Oh+4QifbNRcwFAYwFAYwFAb/QRzdNhgwFAYwFAbav7v/Uy7oaE68MBK5LxLewr/r2NXewLswFAaxJw4wFAbkPRy2PyYwFAaxKhLm1tMwFAazPiQwFAaUGAb/QBrfOx3bvrv/VC/maE4wFAbRPBq6MRO8Qynew8Dp2tjfwb0wFAbx6eju5+by6uns4uH9/f36+vr/GkHjAAAAYnRSTlMAGt+64rnWu/bo8eAA4InH3+DwoN7j4eLi4xP99Nfg4+b+/u9B/eDs1MD1mO7+4PHg2MXa347g7vDizMLN4eG+Pv7i5evs/v79yu7S3/DV7/498Yv24eH+4ufQ3Ozu/v7+y13sRqwAAADLSURBVHjaZc/XDsFgGIBhtDrshlitmk2IrbHFqL2pvXf/+78DPokj7+Fz9qpU/9UXJIlhmPaTaQ6QPaz0mm+5gwkgovcV6GZzd5JtCQwgsxoHOvJO15kleRLAnMgHFIESUEPmawB9ngmelTtipwwfASilxOLyiV5UVUyVAfbG0cCPHig+GBkzAENHS0AstVF6bacZIOzgLmxsHbt2OecNgJC83JERmePUYq8ARGkJx6XtFsdddBQgZE2nPR6CICZhawjA4Fb/chv+399kfR+MMMDGOQAAAABJRU5ErkJggg==");\n background-repeat: no-repeat;\n background-position: 2px center;\n}\n\n.ace_gutter-cell.ace_warning, .ace_icon.ace_warning, .ace_icon.ace_warning_fold {\n background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAmVBMVEX///8AAAD///8AAAAAAABPSzb/5sAAAAB/blH/73z/ulkAAAAAAAD85pkAAAAAAAACAgP/vGz/rkDerGbGrV7/pkQICAf////e0IsAAAD/oED/qTvhrnUAAAD/yHD/njcAAADuv2r/nz//oTj/p064oGf/zHAAAAA9Nir/tFIAAAD/tlTiuWf/tkIAAACynXEAAAAAAAAtIRW7zBpBAAAAM3RSTlMAABR1m7RXO8Ln31Z36zT+neXe5OzooRDfn+TZ4p3h2hTf4t3k3ucyrN1K5+Xaks52Sfs9CXgrAAAAjklEQVR42o3PbQ+CIBQFYEwboPhSYgoYunIqqLn6/z8uYdH8Vmdnu9vz4WwXgN/xTPRD2+sgOcZjsge/whXZgUaYYvT8QnuJaUrjrHUQreGczuEafQCO/SJTufTbroWsPgsllVhq3wJEk2jUSzX3CUEDJC84707djRc5MTAQxoLgupWRwW6UB5fS++NV8AbOZgnsC7BpEAAAAABJRU5ErkJggg==");\n background-repeat: no-repeat;\n background-position: 2px center;\n}\n\n.ace_gutter-cell.ace_info, .ace_icon.ace_info {\n background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAJ0Uk5TAAB2k804AAAAPklEQVQY02NgIB68QuO3tiLznjAwpKTgNyDbMegwisCHZUETUZV0ZqOquBpXj2rtnpSJT1AEnnRmL2OgGgAAIKkRQap2htgAAAAASUVORK5CYII=");\n background-repeat: no-repeat;\n background-position: 2px center;\n}\n.ace_dark .ace_gutter-cell.ace_info, .ace_dark .ace_icon.ace_info {\n background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAChoaGAgIAqKiq+vr6tra1ZWVmUlJSbm5s8PDxubm56enrdgzg3AAAAAXRSTlMAQObYZgAAAClJREFUeNpjYMAPdsMYHegyJZFQBlsUlMFVCWUYKkAZMxZAGdxlDMQBAG+TBP4B6RyJAAAAAElFTkSuQmCC");\n}\n\n.ace_icon_svg.ace_error {\n -webkit-mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAxNiI+CjxnIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJyZWQiIHNoYXBlLXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIj4KPGNpcmNsZSBmaWxsPSJub25lIiBjeD0iOCIgY3k9IjgiIHI9IjciIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPGxpbmUgeDE9IjExIiB5MT0iNSIgeDI9IjUiIHkyPSIxMSIvPgo8bGluZSB4MT0iMTEiIHkxPSIxMSIgeDI9IjUiIHkyPSI1Ii8+CjwvZz4KPC9zdmc+");\n background-color: crimson;\n}\n.ace_icon_svg.ace_warning {\n -webkit-mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAxNiI+CjxnIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJkYXJrb3JhbmdlIiBzaGFwZS1yZW5kZXJpbmc9Imdlb21ldHJpY1ByZWNpc2lvbiI+Cjxwb2x5Z29uIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiIHBvaW50cz0iOCAxIDE1IDE1IDEgMTUgOCAxIi8+CjxyZWN0IHg9IjgiIHk9IjEyIiB3aWR0aD0iMC4wMSIgaGVpZ2h0PSIwLjAxIi8+CjxsaW5lIHgxPSI4IiB5MT0iNiIgeDI9IjgiIHkyPSIxMCIvPgo8L2c+Cjwvc3ZnPg==");\n background-color: darkorange;\n}\n.ace_icon_svg.ace_info {\n -webkit-mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAxNiI+CjxnIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJibHVlIiBzaGFwZS1yZW5kZXJpbmc9Imdlb21ldHJpY1ByZWNpc2lvbiI+CjxjaXJjbGUgZmlsbD0ibm9uZSIgY3g9IjgiIGN5PSI4IiByPSI3IiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjxwb2x5bGluZSBwb2ludHM9IjggMTEgOCA4Ii8+Cjxwb2x5bGluZSBwb2ludHM9IjkgOCA2IDgiLz4KPGxpbmUgeDE9IjEwIiB5MT0iMTEiIHgyPSI2IiB5Mj0iMTEiLz4KPHJlY3QgeD0iOCIgeT0iNSIgd2lkdGg9IjAuMDEiIGhlaWdodD0iMC4wMSIvPgo8L2c+Cjwvc3ZnPg==");\n background-color: royalblue;\n}\n\n.ace_icon_svg.ace_error_fold {\n -webkit-mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAxNiIgZmlsbD0ibm9uZSI+CiAgPHBhdGggZD0ibSAxOC45Mjk4NTEsNy44Mjk4MDc2IGMgMC4xNDYzNTMsNi4zMzc0NjA0IC02LjMyMzE0Nyw3Ljc3Nzg0NDQgLTcuNDc3OTEyLDcuNzc3ODQ0NCAtMi4xMDcyNzI2LC0wLjEyODc1IDUuMTE3Njc4LDAuMzU2MjQ5IDUuMDUxNjk4LC03Ljg3MDA2MTggLTAuNjA0NjcyLC04LjAwMzk3MzQ5IC03LjA3NzI3MDYsLTcuNTYzMTE4OSAtNC44NTczLC03LjQzMDM5NTU2IDEuNjA2LC0wLjExNTE0MjI1IDYuODk3NDg1LDEuMjYyNTQ1OTYgNy4yODM1MTQsNy41MjI2MTI5NiB6IiBmaWxsPSJjcmltc29uIiBzdHJva2Utd2lkdGg9IjIiLz4KICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibSA4LjExNDc1NjIsMi4wNTI5ODI4IGMgMy4zNDkxNjk4LDAgNi4wNjQxMzI4LDIuNjc2ODYyNyA2LjA2NDEzMjgsNS45Nzg5NTMgMCwzLjMwMjExMjIgLTIuNzE0OTYzLDUuOTc4OTIwMiAtNi4wNjQxMzI4LDUuOTc4OTIwMiAtMy4zNDkxNDczLDAgLTYuMDY0MTc3MiwtMi42NzY4MDggLTYuMDY0MTc3MiwtNS45Nzg5MjAyIDAuMDA1MzksLTMuMjk5ODg2MSAyLjcxNzI2NTYsLTUuOTczNjQwOCA2LjA2NDE3NzIsLTUuOTc4OTUzIHogbSAwLC0xLjczNTgyNzE5IGMgLTQuMzIxNDgzNiwwIC03LjgyNDc0MDM4LDMuNDU0MDE4NDkgLTcuODI0NzQwMzgsNy43MTQ3ODAxOSAwLDQuMjYwNzI4MiAzLjUwMzI1Njc4LDcuNzE0NzQ1MiA3LjgyNDc0MDM4LDcuNzE0NzQ1MiA0LjMyMTQ0OTgsMCA3LjgyNDY5OTgsLTMuNDU0MDE3IDcuODI0Njk5OCwtNy43MTQ3NDUyIDAsLTIuMDQ2MDkxNCAtMC44MjQzOTIsLTQuMDA4MzY3MiAtMi4yOTE3NTYsLTUuNDU1MTc0NiBDIDEyLjE4MDIyNSwxLjEyOTk2NDggMTAuMTkwMDEzLDAuMzE3MTU1NjEgOC4xMTQ3NTYyLDAuMzE3MTU1NjEgWiBNIDYuOTM3NDU2Myw4LjI0MDU5ODUgNC42NzE4Njg1LDEwLjQ4NTg1MiA2LjAwODY4MTQsMTEuODc2NzI4IDguMzE3MDAzNSw5LjYwMDc5MTEgMTAuNjI1MzM3LDExLjg3NjcyOCAxMS45NjIxMzgsMTAuNDg1ODUyIDkuNjk2NTUwOCw4LjI0MDU5ODUgMTEuOTYyMTM4LDYuMDA2ODA2NiAxMC41NzMyNDYsNC42Mzc0MzM1IDguMzE3MDAzNSw2Ljg3MzQyOTcgNi4wNjA3NjA3LDQuNjM3NDMzNSA0LjY3MTg2ODUsNi4wMDY4MDY2IFoiIGZpbGw9ImNyaW1zb24iIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4=");\n background-color: crimson;\n}\n.ace_icon_svg.ace_warning_fold {\n -webkit-mask-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAyMCAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNC43NzY5IDE0LjczMzdMOC42NTE5MiAyLjQ4MzY5QzguMzI5NDYgMS44Mzg3NyA3LjQwOTEzIDEuODM4NzcgNy4wODY2NyAyLjQ4MzY5TDAuOTYxNjY5IDE0LjczMzdDMC42NzA3NzUgMTUuMzE1NSAxLjA5MzgzIDE2IDEuNzQ0MjkgMTZIMTMuOTk0M0MxNC42NDQ4IDE2IDE1LjA2NzggMTUuMzE1NSAxNC43NzY5IDE0LjczMzdaTTMuMTYwMDcgMTQuMjVMNy44NjkyOSA0LjgzMTU2TDEyLjU3ODUgMTQuMjVIMy4xNjAwN1pNOC43NDQyOSAxMS42MjVWMTMuMzc1SDYuOTk0MjlWMTEuNjI1SDguNzQ0MjlaTTYuOTk0MjkgMTAuNzVWNy4yNUg4Ljc0NDI5VjEwLjc1SDYuOTk0MjlaIiBmaWxsPSIjRUM3MjExIi8+CjxwYXRoIGQ9Ik0xMS4xOTkxIDIuOTUyMzhDMTAuODgwOSAyLjMxNDY3IDEwLjM1MzcgMS44MDUyNiA5LjcwNTUgMS41MDlMMTEuMDQxIDEuMDY5NzhDMTEuNjg4MyAwLjk0OTgxNCAxMi4zMzcgMS4yNzI2MyAxMi42MzE3IDEuODYxNDFMMTcuNjEzNiAxMS44MTYxQzE4LjM1MjcgMTMuMjkyOSAxNy41OTM4IDE1LjA4MDQgMTYuMDE4IDE1LjU3NDVDMTYuNDA0NCAxNC40NTA3IDE2LjMyMzEgMTMuMjE4OCAxNS43OTI0IDEyLjE1NTVMMTEuMTk5MSAyLjk1MjM4WiIgZmlsbD0iI0VDNzIxMSIvPgo8L3N2Zz4=");\n background-color: darkorange;\n}\n\n.ace_scrollbar {\n contain: strict;\n position: absolute;\n right: 0;\n bottom: 0;\n z-index: 6;\n}\n\n.ace_scrollbar-inner {\n position: absolute;\n cursor: text;\n left: 0;\n top: 0;\n}\n\n.ace_scrollbar-v{\n overflow-x: hidden;\n overflow-y: scroll;\n top: 0;\n}\n\n.ace_scrollbar-h {\n overflow-x: scroll;\n overflow-y: hidden;\n left: 0;\n}\n\n.ace_print-margin {\n position: absolute;\n height: 100%;\n}\n\n.ace_text-input {\n position: absolute;\n z-index: 0;\n width: 0.5em;\n height: 1em;\n opacity: 0;\n background: transparent;\n -moz-appearance: none;\n appearance: none;\n border: none;\n resize: none;\n outline: none;\n overflow: hidden;\n font: inherit;\n padding: 0 1px;\n margin: 0 -1px;\n contain: strict;\n -ms-user-select: text;\n -moz-user-select: text;\n -webkit-user-select: text;\n user-select: text;\n /*with `pre-line` chrome inserts   instead of space*/\n white-space: pre!important;\n}\n.ace_text-input.ace_composition {\n background: transparent;\n color: inherit;\n z-index: 1000;\n opacity: 1;\n}\n.ace_composition_placeholder { color: transparent }\n.ace_composition_marker { \n border-bottom: 1px solid;\n position: absolute;\n border-radius: 0;\n margin-top: 1px;\n}\n\n[ace_nocontext=true] {\n transform: none!important;\n filter: none!important;\n clip-path: none!important;\n mask : none!important;\n contain: none!important;\n perspective: none!important;\n mix-blend-mode: initial!important;\n z-index: auto;\n}\n\n.ace_layer {\n z-index: 1;\n position: absolute;\n overflow: hidden;\n /* workaround for chrome bug https://github.com/ajaxorg/ace/issues/2312*/\n word-wrap: normal;\n white-space: pre;\n height: 100%;\n width: 100%;\n box-sizing: border-box;\n /* setting pointer-events: auto; on node under the mouse, which changes\n during scroll, will break mouse wheel scrolling in Safari */\n pointer-events: none;\n}\n\n.ace_gutter-layer {\n position: relative;\n width: auto;\n text-align: right;\n pointer-events: auto;\n height: 1000000px;\n contain: style size layout;\n}\n\n.ace_text-layer {\n font: inherit !important;\n position: absolute;\n height: 1000000px;\n width: 1000000px;\n contain: style size layout;\n}\n\n.ace_text-layer > .ace_line, .ace_text-layer > .ace_line_group {\n contain: style size layout;\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n}\n\n.ace_hidpi .ace_text-layer,\n.ace_hidpi .ace_gutter-layer,\n.ace_hidpi .ace_content,\n.ace_hidpi .ace_gutter {\n contain: strict;\n}\n.ace_hidpi .ace_text-layer > .ace_line, \n.ace_hidpi .ace_text-layer > .ace_line_group {\n contain: strict;\n}\n\n.ace_cjk {\n display: inline-block;\n text-align: center;\n}\n\n.ace_cursor-layer {\n z-index: 4;\n}\n\n.ace_cursor {\n z-index: 4;\n position: absolute;\n box-sizing: border-box;\n border-left: 2px solid;\n /* workaround for smooth cursor repaintng whole screen in chrome */\n transform: translatez(0);\n}\n\n.ace_multiselect .ace_cursor {\n border-left-width: 1px;\n}\n\n.ace_slim-cursors .ace_cursor {\n border-left-width: 1px;\n}\n\n.ace_overwrite-cursors .ace_cursor {\n border-left-width: 0;\n border-bottom: 1px solid;\n}\n\n.ace_hidden-cursors .ace_cursor {\n opacity: 0.2;\n}\n\n.ace_hasPlaceholder .ace_hidden-cursors .ace_cursor {\n opacity: 0;\n}\n\n.ace_smooth-blinking .ace_cursor {\n transition: opacity 0.18s;\n}\n\n.ace_animate-blinking .ace_cursor {\n animation-duration: 1000ms;\n animation-timing-function: step-end;\n animation-name: blink-ace-animate;\n animation-iteration-count: infinite;\n}\n\n.ace_animate-blinking.ace_smooth-blinking .ace_cursor {\n animation-duration: 1000ms;\n animation-timing-function: ease-in-out;\n animation-name: blink-ace-animate-smooth;\n}\n \n@keyframes blink-ace-animate {\n from, to { opacity: 1; }\n 60% { opacity: 0; }\n}\n\n@keyframes blink-ace-animate-smooth {\n from, to { opacity: 1; }\n 45% { opacity: 1; }\n 60% { opacity: 0; }\n 85% { opacity: 0; }\n}\n\n.ace_marker-layer .ace_step, .ace_marker-layer .ace_stack {\n position: absolute;\n z-index: 3;\n}\n\n.ace_marker-layer .ace_selection {\n position: absolute;\n z-index: 5;\n}\n\n.ace_marker-layer .ace_bracket {\n position: absolute;\n z-index: 6;\n}\n\n.ace_marker-layer .ace_error_bracket {\n position: absolute;\n border-bottom: 1px solid #DE5555;\n border-radius: 0;\n}\n\n.ace_marker-layer .ace_active-line {\n position: absolute;\n z-index: 2;\n}\n\n.ace_marker-layer .ace_selected-word {\n position: absolute;\n z-index: 4;\n box-sizing: border-box;\n}\n\n.ace_line .ace_fold {\n box-sizing: border-box;\n\n display: inline-block;\n height: 11px;\n margin-top: -2px;\n vertical-align: middle;\n\n background-image:\n url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),\n url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpi+P//fxgTAwPDBxDxD078RSX+YeEyDFMCIMAAI3INmXiwf2YAAAAASUVORK5CYII=");\n background-repeat: no-repeat, repeat-x;\n background-position: center center, top left;\n color: transparent;\n\n border: 1px solid black;\n border-radius: 2px;\n\n cursor: pointer;\n pointer-events: auto;\n}\n\n.ace_dark .ace_fold {\n}\n\n.ace_fold:hover{\n background-image:\n url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),\n url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACBJREFUeNpi+P//fz4TAwPDZxDxD5X4i5fLMEwJgAADAEPVDbjNw87ZAAAAAElFTkSuQmCC");\n}\n\n.ace_tooltip {\n background-color: #f5f5f5;\n border: 1px solid gray;\n border-radius: 1px;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);\n color: black;\n max-width: 100%;\n padding: 3px 4px;\n position: fixed;\n z-index: 999999;\n box-sizing: border-box;\n cursor: default;\n white-space: pre-wrap;\n word-wrap: break-word;\n line-height: normal;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n pointer-events: none;\n overflow: auto;\n max-width: min(60em, 66vw);\n overscroll-behavior: contain;\n}\n.ace_tooltip pre {\n white-space: pre-wrap;\n}\n\n.ace_tooltip.ace_dark {\n background-color: #636363;\n color: #fff;\n}\n\n.ace_tooltip:focus {\n outline: 1px solid #5E9ED6;\n}\n\n.ace_icon {\n display: inline-block;\n width: 18px;\n vertical-align: top;\n}\n\n.ace_icon_svg {\n display: inline-block;\n width: 12px;\n vertical-align: top;\n -webkit-mask-repeat: no-repeat;\n -webkit-mask-size: 12px;\n -webkit-mask-position: center;\n}\n\n.ace_folding-enabled > .ace_gutter-cell, .ace_folding-enabled > .ace_gutter-cell_svg-icons {\n padding-right: 13px;\n}\n\n.ace_fold-widget {\n box-sizing: border-box;\n\n margin: 0 -12px 0 1px;\n display: none;\n width: 11px;\n vertical-align: top;\n\n background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42mWKsQ0AMAzC8ixLlrzQjzmBiEjp0A6WwBCSPgKAXoLkqSot7nN3yMwR7pZ32NzpKkVoDBUxKAAAAABJRU5ErkJggg==");\n background-repeat: no-repeat;\n background-position: center;\n\n border-radius: 3px;\n \n border: 1px solid transparent;\n cursor: pointer;\n}\n\n.ace_folding-enabled .ace_fold-widget {\n display: inline-block; \n}\n\n.ace_fold-widget.ace_end {\n background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42m3HwQkAMAhD0YzsRchFKI7sAikeWkrxwScEB0nh5e7KTPWimZki4tYfVbX+MNl4pyZXejUO1QAAAABJRU5ErkJggg==");\n}\n\n.ace_fold-widget.ace_closed {\n background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAGCAYAAAAG5SQMAAAAOUlEQVR42jXKwQkAMAgDwKwqKD4EwQ26sSOkVWjgIIHAzPiCgaqiqnJHZnKICBERHN194O5b9vbLuAVRL+l0YWnZAAAAAElFTkSuQmCCXA==");\n}\n\n.ace_fold-widget:hover {\n border: 1px solid rgba(0, 0, 0, 0.3);\n background-color: rgba(255, 255, 255, 0.2);\n box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);\n}\n\n.ace_fold-widget:active {\n border: 1px solid rgba(0, 0, 0, 0.4);\n background-color: rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);\n}\n/**\n * Dark version for fold widgets\n */\n.ace_dark .ace_fold-widget {\n background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHklEQVQIW2P4//8/AzoGEQ7oGCaLLAhWiSwB146BAQCSTPYocqT0AAAAAElFTkSuQmCC");\n}\n.ace_dark .ace_fold-widget.ace_end {\n background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQIW2P4//8/AxQ7wNjIAjDMgC4AxjCVKBirIAAF0kz2rlhxpAAAAABJRU5ErkJggg==");\n}\n.ace_dark .ace_fold-widget.ace_closed {\n background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAHElEQVQIW2P4//+/AxAzgDADlOOAznHAKgPWAwARji8UIDTfQQAAAABJRU5ErkJggg==");\n}\n.ace_dark .ace_fold-widget:hover {\n box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);\n background-color: rgba(255, 255, 255, 0.1);\n}\n.ace_dark .ace_fold-widget:active {\n box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);\n}\n\n.ace_inline_button {\n border: 1px solid lightgray;\n display: inline-block;\n margin: -1px 8px;\n padding: 0 5px;\n pointer-events: auto;\n cursor: pointer;\n}\n.ace_inline_button:hover {\n border-color: gray;\n background: rgba(200,200,200,0.2);\n display: inline-block;\n pointer-events: auto;\n}\n\n.ace_fold-widget.ace_invalid {\n background-color: #FFB4B4;\n border-color: #DE5555;\n}\n\n.ace_fade-fold-widgets .ace_fold-widget {\n transition: opacity 0.4s ease 0.05s;\n opacity: 0;\n}\n\n.ace_fade-fold-widgets:hover .ace_fold-widget {\n transition: opacity 0.05s ease 0.05s;\n opacity:1;\n}\n\n.ace_underline {\n text-decoration: underline;\n}\n\n.ace_bold {\n font-weight: bold;\n}\n\n.ace_nobold .ace_bold {\n font-weight: normal;\n}\n\n.ace_italic {\n font-style: italic;\n}\n\n\n.ace_error-marker {\n background-color: rgba(255, 0, 0,0.2);\n position: absolute;\n z-index: 9;\n}\n\n.ace_highlight-marker {\n background-color: rgba(255, 255, 0,0.2);\n position: absolute;\n z-index: 8;\n}\n\n.ace_mobile-menu {\n position: absolute;\n line-height: 1.5;\n border-radius: 4px;\n -ms-user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n user-select: none;\n background: white;\n box-shadow: 1px 3px 2px grey;\n border: 1px solid #dcdcdc;\n color: black;\n}\n.ace_dark > .ace_mobile-menu {\n background: #333;\n color: #ccc;\n box-shadow: 1px 3px 2px grey;\n border: 1px solid #444;\n\n}\n.ace_mobile-button {\n padding: 2px;\n cursor: pointer;\n overflow: hidden;\n}\n.ace_mobile-button:hover {\n background-color: #eee;\n opacity:1;\n}\n.ace_mobile-button:active {\n background-color: #ddd;\n}\n\n.ace_placeholder {\n font-family: arial;\n transform: scale(0.9);\n transform-origin: left;\n white-space: pre;\n opacity: 0.7;\n margin: 0 10px;\n}\n\n.ace_ghost_text {\n opacity: 0.5;\n font-style: italic;\n white-space: pre;\n}\n\n.ace_screenreader-only {\n position:absolute;\n left:-10000px;\n top:auto;\n width:1px;\n height:1px;\n overflow:hidden;\n}'}),define("ace/layer/decorators",["require","exports","module","ace/lib/dom","ace/lib/oop","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("../lib/dom"),i=e("../lib/oop"),s=e("../lib/event_emitter").EventEmitter,o=function(){function e(e,t){this.canvas=r.createElement("canvas"),this.renderer=t,this.pixelRatio=1,this.maxHeight=t.layerConfig.maxHeight,this.lineHeight=t.layerConfig.lineHeight,this.canvasHeight=e.parent.scrollHeight,this.heightRatio=this.canvasHeight/this.maxHeight,this.canvasWidth=e.width,this.minDecorationHeight=2*this.pixelRatio|0,this.halfMinDecorationHeight=this.minDecorationHeight/2|0,this.canvas.width=this.canvasWidth,this.canvas.height=this.canvasHeight,this.canvas.style.top="0px",this.canvas.style.right="0px",this.canvas.style.zIndex="7px",this.canvas.style.position="absolute",this.colors={},this.colors.dark={error:"rgba(255, 18, 18, 1)",warning:"rgba(18, 136, 18, 1)",info:"rgba(18, 18, 136, 1)"},this.colors.light={error:"rgb(255,51,51)",warning:"rgb(32,133,72)",info:"rgb(35,68,138)"},e.element.appendChild(this.canvas)}return e.prototype.$updateDecorators=function(e){function i(e,t){return e.priorityt.priority?1:0}var t=this.renderer.theme.isDark===!0?this.colors.dark:this.colors.light;if(e){this.maxHeight=e.maxHeight,this.lineHeight=e.lineHeight,this.canvasHeight=e.height;var n=(e.lastRow+1)*this.lineHeight;nthis.canvasHeight&&(v=this.canvasHeight-this.halfMinDecorationHeight),h=Math.round(v-this.halfMinDecorationHeight),p=Math.round(v+this.halfMinDecorationHeight)}r.fillStyle=t[s[a].type]||null,r.fillRect(0,c,this.canvasWidth,p-h)}}var m=this.renderer.session.selection.getCursor();if(m){var l=this.compensateFoldRows(m.row,u),c=Math.round((m.row-l)*this.lineHeight*this.heightRatio);r.fillStyle="rgba(0, 0, 0, 0.5)",r.fillRect(0,c,this.canvasWidth,2)}},e.prototype.compensateFoldRows=function(e,t){var n=0;if(t&&t.length>0)for(var r=0;rt[r].start.row&&e=t[r].end.row&&(n+=t[r].end.row-t[r].start.row);return n},e}();i.implement(o.prototype,s),t.Decorator=o}),define("ace/virtual_renderer",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/lang","ace/config","ace/layer/gutter","ace/layer/marker","ace/layer/text","ace/layer/cursor","ace/scrollbar","ace/scrollbar","ace/scrollbar_custom","ace/scrollbar_custom","ace/renderloop","ace/layer/font_metrics","ace/lib/event_emitter","ace/css/editor-css","ace/layer/decorators","ace/lib/useragent"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/dom"),s=e("./lib/lang"),o=e("./config"),u=e("./layer/gutter").Gutter,a=e("./layer/marker").Marker,f=e("./layer/text").Text,l=e("./layer/cursor").Cursor,c=e("./scrollbar").HScrollBar,h=e("./scrollbar").VScrollBar,p=e("./scrollbar_custom").HScrollBar,d=e("./scrollbar_custom").VScrollBar,v=e("./renderloop").RenderLoop,m=e("./layer/font_metrics").FontMetrics,g=e("./lib/event_emitter").EventEmitter,y=e("./css/editor-css"),b=e("./layer/decorators").Decorator,w=e("./lib/useragent");i.importCssString(y,"ace_editor.css",!1);var E=function(){function e(e,t){var n=this;this.container=e||i.createElement("div"),i.addCssClass(this.container,"ace_editor"),i.HI_DPI&&i.addCssClass(this.container,"ace_hidpi"),this.setTheme(t),o.get("useStrictCSP")==null&&o.set("useStrictCSP",!1),this.$gutter=i.createElement("div"),this.$gutter.className="ace_gutter",this.container.appendChild(this.$gutter),this.$gutter.setAttribute("aria-hidden","true"),this.scroller=i.createElement("div"),this.scroller.className="ace_scroller",this.container.appendChild(this.scroller),this.content=i.createElement("div"),this.content.className="ace_content",this.scroller.appendChild(this.content),this.$gutterLayer=new u(this.$gutter),this.$gutterLayer.on("changeGutterWidth",this.onGutterResize.bind(this)),this.$markerBack=new a(this.content);var r=this.$textLayer=new f(this.content);this.canvas=r.element,this.$markerFront=new a(this.content),this.$cursorLayer=new l(this.content),this.$horizScroll=!1,this.$vScroll=!1,this.scrollBar=this.scrollBarV=new h(this.container,this),this.scrollBarH=new c(this.container,this),this.scrollBarV.on("scroll",function(e){n.$scrollAnimation||n.session.setScrollTop(e.data-n.scrollMargin.top)}),this.scrollBarH.on("scroll",function(e){n.$scrollAnimation||n.session.setScrollLeft(e.data-n.scrollMargin.left)}),this.scrollTop=0,this.scrollLeft=0,this.cursorPos={row:0,column:0},this.$fontMetrics=new m(this.container),this.$textLayer.$setFontMetrics(this.$fontMetrics),this.$textLayer.on("changeCharacterSize",function(e){n.updateCharacterSize(),n.onResize(!0,n.gutterWidth,n.$size.width,n.$size.height),n._signal("changeCharacterSize",e)}),this.$size={width:0,height:0,scrollerHeight:0,scrollerWidth:0,$dirty:!0},this.layerConfig={width:1,padding:0,firstRow:0,firstRowScreen:0,lastRow:0,lineHeight:0,characterWidth:0,minHeight:1,maxHeight:1,offset:0,height:1,gutterOffset:1},this.scrollMargin={left:0,right:0,top:0,bottom:0,v:0,h:0},this.margin={left:0,right:0,top:0,bottom:0,v:0,h:0},this.$keepTextAreaAtCursor=!w.isIOS,this.$loop=new v(this.$renderChanges.bind(this),this.container.ownerDocument.defaultView),this.$loop.schedule(this.CHANGE_FULL),this.updateCharacterSize(),this.setPadding(4),this.$addResizeObserver(),o.resetOptions(this),o._signal("renderer",this)}return e.prototype.updateCharacterSize=function(){this.$textLayer.allowBoldFonts!=this.$allowBoldFonts&&(this.$allowBoldFonts=this.$textLayer.allowBoldFonts,this.setStyle("ace_nobold",!this.$allowBoldFonts)),this.layerConfig.characterWidth=this.characterWidth=this.$textLayer.getCharacterWidth(),this.layerConfig.lineHeight=this.lineHeight=this.$textLayer.getLineHeight(),this.$updatePrintMargin(),i.setStyle(this.scroller.style,"line-height",this.lineHeight+"px")},e.prototype.setSession=function(e){this.session&&this.session.doc.off("changeNewLineMode",this.onChangeNewLineMode),this.session=e,e&&this.scrollMargin.top&&e.getScrollTop()<=0&&e.setScrollTop(-this.scrollMargin.top),this.$cursorLayer.setSession(e),this.$markerBack.setSession(e),this.$markerFront.setSession(e),this.$gutterLayer.setSession(e),this.$textLayer.setSession(e);if(!e)return;this.$loop.schedule(this.CHANGE_FULL),this.session.$setFontMetrics(this.$fontMetrics),this.scrollBarH.scrollLeft=this.scrollBarV.scrollTop=null,this.onChangeNewLineMode=this.onChangeNewLineMode.bind(this),this.onChangeNewLineMode(),this.session.doc.on("changeNewLineMode",this.onChangeNewLineMode)},e.prototype.updateLines=function(e,t,n){t===undefined&&(t=Infinity),this.$changedLines?(this.$changedLines.firstRow>e&&(this.$changedLines.firstRow=e),this.$changedLines.lastRowthis.layerConfig.lastRow)return;this.$loop.schedule(this.CHANGE_LINES)},e.prototype.onChangeNewLineMode=function(){this.$loop.schedule(this.CHANGE_TEXT),this.$textLayer.$updateEolChar(),this.session.$bidiHandler.setEolChar(this.$textLayer.EOL_CHAR)},e.prototype.onChangeTabSize=function(){this.$loop.schedule(this.CHANGE_TEXT|this.CHANGE_MARKER),this.$textLayer.onChangeTabSize()},e.prototype.updateText=function(){this.$loop.schedule(this.CHANGE_TEXT)},e.prototype.updateFull=function(e){e?this.$renderChanges(this.CHANGE_FULL,!0):this.$loop.schedule(this.CHANGE_FULL)},e.prototype.updateFontSize=function(){this.$textLayer.checkForSizeChanges()},e.prototype.$updateSizeAsync=function(){this.$loop.pending?this.$size.$dirty=!0:this.onResize()},e.prototype.onResize=function(e,t,n,r){if(this.resizing>2)return;this.resizing>0?this.resizing++:this.resizing=e?1:0;var i=this.container;r||(r=i.clientHeight||i.scrollHeight),n||(n=i.clientWidth||i.scrollWidth);var s=this.$updateCachedSize(e,t,n,r);this.$resizeTimer&&this.$resizeTimer.cancel();if(!this.$size.scrollerHeight||!n&&!r)return this.resizing=0;e&&(this.$gutterLayer.$padding=null),e?this.$renderChanges(s|this.$changes,!0):this.$loop.schedule(s|this.$changes),this.resizing&&(this.resizing=0),this.scrollBarH.scrollLeft=this.scrollBarV.scrollTop=null,this.$customScrollbar&&this.$updateCustomScrollbar(!0)},e.prototype.$updateCachedSize=function(e,t,n,r){r-=this.$extraHeight||0;var s=0,o=this.$size,u={width:o.width,height:o.height,scrollerHeight:o.scrollerHeight,scrollerWidth:o.scrollerWidth};r&&(e||o.height!=r)&&(o.height=r,s|=this.CHANGE_SIZE,o.scrollerHeight=o.height,this.$horizScroll&&(o.scrollerHeight-=this.scrollBarH.getHeight()),this.scrollBarV.setHeight(o.scrollerHeight),this.scrollBarV.element.style.bottom=this.scrollBarH.getHeight()+"px",s|=this.CHANGE_SCROLL);if(n&&(e||o.width!=n)){s|=this.CHANGE_SIZE,o.width=n,t==null&&(t=this.$showGutter?this.$gutter.offsetWidth:0),this.gutterWidth=t,i.setStyle(this.scrollBarH.element.style,"left",t+"px"),i.setStyle(this.scroller.style,"left",t+this.margin.left+"px"),o.scrollerWidth=Math.max(0,n-t-this.scrollBarV.getWidth()-this.margin.h),i.setStyle(this.$gutter.style,"left",this.margin.left+"px");var a=this.scrollBarV.getWidth()+"px";i.setStyle(this.scrollBarH.element.style,"right",a),i.setStyle(this.scroller.style,"right",a),i.setStyle(this.scroller.style,"bottom",this.scrollBarH.getHeight()),this.scrollBarH.setWidth(o.scrollerWidth);if(this.session&&this.session.getUseWrapMode()&&this.adjustWrapLimit()||e)s|=this.CHANGE_FULL}return o.$dirty=!n||!r,s&&this._signal("resize",u),s},e.prototype.onGutterResize=function(e){var t=this.$showGutter?e:0;t!=this.gutterWidth&&(this.$changes|=this.$updateCachedSize(!0,t,this.$size.width,this.$size.height)),this.session.getUseWrapMode()&&this.adjustWrapLimit()?this.$loop.schedule(this.CHANGE_FULL):this.$size.$dirty?this.$loop.schedule(this.CHANGE_FULL):this.$computeLayerConfig()},e.prototype.adjustWrapLimit=function(){var e=this.$size.scrollerWidth-this.$padding*2,t=Math.floor(e/this.characterWidth);return this.session.adjustWrapLimit(t,this.$showPrintMargin&&this.$printMarginColumn)},e.prototype.setAnimatedScroll=function(e){this.setOption("animatedScroll",e)},e.prototype.getAnimatedScroll=function(){return this.$animatedScroll},e.prototype.setShowInvisibles=function(e){this.setOption("showInvisibles",e),this.session.$bidiHandler.setShowInvisibles(e)},e.prototype.getShowInvisibles=function(){return this.getOption("showInvisibles")},e.prototype.getDisplayIndentGuides=function(){return this.getOption("displayIndentGuides")},e.prototype.setDisplayIndentGuides=function(e){this.setOption("displayIndentGuides",e)},e.prototype.getHighlightIndentGuides=function(){return this.getOption("highlightIndentGuides")},e.prototype.setHighlightIndentGuides=function(e){this.setOption("highlightIndentGuides",e)},e.prototype.setShowPrintMargin=function(e){this.setOption("showPrintMargin",e)},e.prototype.getShowPrintMargin=function(){return this.getOption("showPrintMargin")},e.prototype.setPrintMarginColumn=function(e){this.setOption("printMarginColumn",e)},e.prototype.getPrintMarginColumn=function(){return this.getOption("printMarginColumn")},e.prototype.getShowGutter=function(){return this.getOption("showGutter")},e.prototype.setShowGutter=function(e){return this.setOption("showGutter",e)},e.prototype.getFadeFoldWidgets=function(){return this.getOption("fadeFoldWidgets")},e.prototype.setFadeFoldWidgets=function(e){this.setOption("fadeFoldWidgets",e)},e.prototype.setHighlightGutterLine=function(e){this.setOption("highlightGutterLine",e)},e.prototype.getHighlightGutterLine=function(){return this.getOption("highlightGutterLine")},e.prototype.$updatePrintMargin=function(){if(!this.$showPrintMargin&&!this.$printMarginEl)return;if(!this.$printMarginEl){var e=i.createElement("div");e.className="ace_layer ace_print-margin-layer",this.$printMarginEl=i.createElement("div"),this.$printMarginEl.className="ace_print-margin",e.appendChild(this.$printMarginEl),this.content.insertBefore(e,this.content.firstChild)}var t=this.$printMarginEl.style;t.left=Math.round(this.characterWidth*this.$printMarginColumn+this.$padding)+"px",t.visibility=this.$showPrintMargin?"visible":"hidden",this.session&&this.session.$wrap==-1&&this.adjustWrapLimit()},e.prototype.getContainerElement=function(){return this.container},e.prototype.getMouseEventTarget=function(){return this.scroller},e.prototype.getTextAreaContainer=function(){return this.container},e.prototype.$moveTextAreaToCursor=function(){if(this.$isMousePressed)return;var e=this.textarea.style,t=this.$composition;if(!this.$keepTextAreaAtCursor&&!t){i.translate(this.textarea,-100,0);return}var n=this.$cursorLayer.$pixelPos;if(!n)return;t&&t.markerRange&&(n=this.$cursorLayer.getPixelPosition(t.markerRange.start,!0));var r=this.layerConfig,s=n.top,o=n.left;s-=r.offset;var u=t&&t.useTextareaForIME||w.isMobile?this.lineHeight:1;if(s<0||s>r.height-u){i.translate(this.textarea,0,0);return}var a=1,f=this.$size.height-u;if(!t)s+=this.lineHeight;else if(t.useTextareaForIME){var l=this.textarea.value;a=this.characterWidth*this.session.$getStringScreenWidth(l)[0]}else s+=this.lineHeight+2;o-=this.scrollLeft,o>this.$size.scrollerWidth-a&&(o=this.$size.scrollerWidth-a),o+=this.gutterWidth+this.margin.left,i.setStyle(e,"height",u+"px"),i.setStyle(e,"width",a+"px"),i.translate(this.textarea,Math.min(o,this.$size.scrollerWidth-a),Math.min(s,f))},e.prototype.getFirstVisibleRow=function(){return this.layerConfig.firstRow},e.prototype.getFirstFullyVisibleRow=function(){return this.layerConfig.firstRow+(this.layerConfig.offset===0?0:1)},e.prototype.getLastFullyVisibleRow=function(){var e=this.layerConfig,t=e.lastRow,n=this.session.documentToScreenRow(t,0)*e.lineHeight;return n-this.session.getScrollTop()>e.height-e.lineHeight?t-1:t},e.prototype.getLastVisibleRow=function(){return this.layerConfig.lastRow},e.prototype.setPadding=function(e){this.$padding=e,this.$textLayer.setPadding(e),this.$cursorLayer.setPadding(e),this.$markerFront.setPadding(e),this.$markerBack.setPadding(e),this.$loop.schedule(this.CHANGE_FULL),this.$updatePrintMargin()},e.prototype.setScrollMargin=function(e,t,n,r){var i=this.scrollMargin;i.top=e|0,i.bottom=t|0,i.right=r|0,i.left=n|0,i.v=i.top+i.bottom,i.h=i.left+i.right,i.top&&this.scrollTop<=0&&this.session&&this.session.setScrollTop(-i.top),this.updateFull()},e.prototype.setMargin=function(e,t,n,r){var i=this.margin;i.top=e|0,i.bottom=t|0,i.right=r|0,i.left=n|0,i.v=i.top+i.bottom,i.h=i.left+i.right,this.$updateCachedSize(!0,this.gutterWidth,this.$size.width,this.$size.height),this.updateFull()},e.prototype.getHScrollBarAlwaysVisible=function(){return this.$hScrollBarAlwaysVisible},e.prototype.setHScrollBarAlwaysVisible=function(e){this.setOption("hScrollBarAlwaysVisible",e)},e.prototype.getVScrollBarAlwaysVisible=function(){return this.$vScrollBarAlwaysVisible},e.prototype.setVScrollBarAlwaysVisible=function(e){this.setOption("vScrollBarAlwaysVisible",e)},e.prototype.$updateScrollBarV=function(){var e=this.layerConfig.maxHeight,t=this.$size.scrollerHeight;!this.$maxLines&&this.$scrollPastEnd&&(e-=(t-this.lineHeight)*this.$scrollPastEnd,this.scrollTop>e-t&&(e=this.scrollTop+t,this.scrollBarV.scrollTop=null)),this.scrollBarV.setScrollHeight(e+this.scrollMargin.v),this.scrollBarV.setScrollTop(this.scrollTop+this.scrollMargin.top)},e.prototype.$updateScrollBarH=function(){this.scrollBarH.setScrollWidth(this.layerConfig.width+2*this.$padding+this.scrollMargin.h),this.scrollBarH.setScrollLeft(this.scrollLeft+this.scrollMargin.left)},e.prototype.freeze=function(){this.$frozen=!0},e.prototype.unfreeze=function(){this.$frozen=!1},e.prototype.$renderChanges=function(e,t){this.$changes&&(e|=this.$changes,this.$changes=0);if(!this.session||!this.container.offsetWidth||this.$frozen||!e&&!t){this.$changes|=e;return}if(this.$size.$dirty)return this.$changes|=e,this.onResize(!0);this.lineHeight||this.$textLayer.checkForSizeChanges(),this._signal("beforeRender",e),this.session&&this.session.$bidiHandler&&this.session.$bidiHandler.updateCharacterWidths(this.$fontMetrics);var n=this.layerConfig;if(e&this.CHANGE_FULL||e&this.CHANGE_SIZE||e&this.CHANGE_TEXT||e&this.CHANGE_LINES||e&this.CHANGE_SCROLL||e&this.CHANGE_H_SCROLL){e|=this.$computeLayerConfig()|this.$loop.clear();if(n.firstRow!=this.layerConfig.firstRow&&n.firstRowScreen==this.layerConfig.firstRowScreen){var r=this.scrollTop+(n.firstRow-Math.max(this.layerConfig.firstRow,0))*this.lineHeight;r>0&&(this.scrollTop=r,e|=this.CHANGE_SCROLL,e|=this.$computeLayerConfig()|this.$loop.clear())}n=this.layerConfig,this.$updateScrollBarV(),e&this.CHANGE_H_SCROLL&&this.$updateScrollBarH(),i.translate(this.content,-this.scrollLeft,-n.offset);var s=n.width+2*this.$padding+"px",o=n.minHeight+"px";i.setStyle(this.content.style,"width",s),i.setStyle(this.content.style,"height",o)}e&this.CHANGE_H_SCROLL&&(i.translate(this.content,-this.scrollLeft,-n.offset),this.scroller.className=this.scrollLeft<=0?"ace_scroller ":"ace_scroller ace_scroll-left ",this.enableKeyboardAccessibility&&(this.scroller.className+=this.keyboardFocusClassName));if(e&this.CHANGE_FULL){this.$changedLines=null,this.$textLayer.update(n),this.$showGutter&&this.$gutterLayer.update(n),this.$customScrollbar&&this.$scrollDecorator.$updateDecorators(n),this.$markerBack.update(n),this.$markerFront.update(n),this.$cursorLayer.update(n),this.$moveTextAreaToCursor(),this._signal("afterRender",e);return}if(e&this.CHANGE_SCROLL){this.$changedLines=null,e&this.CHANGE_TEXT||e&this.CHANGE_LINES?this.$textLayer.update(n):this.$textLayer.scrollLines(n),this.$showGutter&&(e&this.CHANGE_GUTTER||e&this.CHANGE_LINES?this.$gutterLayer.update(n):this.$gutterLayer.scrollLines(n)),this.$customScrollbar&&this.$scrollDecorator.$updateDecorators(n),this.$markerBack.update(n),this.$markerFront.update(n),this.$cursorLayer.update(n),this.$moveTextAreaToCursor(),this._signal("afterRender",e);return}e&this.CHANGE_TEXT?(this.$changedLines=null,this.$textLayer.update(n),this.$showGutter&&this.$gutterLayer.update(n),this.$customScrollbar&&this.$scrollDecorator.$updateDecorators(n)):e&this.CHANGE_LINES?((this.$updateLines()||e&this.CHANGE_GUTTER&&this.$showGutter)&&this.$gutterLayer.update(n),this.$customScrollbar&&this.$scrollDecorator.$updateDecorators(n)):e&this.CHANGE_TEXT||e&this.CHANGE_GUTTER?(this.$showGutter&&this.$gutterLayer.update(n),this.$customScrollbar&&this.$scrollDecorator.$updateDecorators(n)):e&this.CHANGE_CURSOR&&(this.$highlightGutterLine&&this.$gutterLayer.updateLineHighlight(n),this.$customScrollbar&&this.$scrollDecorator.$updateDecorators(n)),e&this.CHANGE_CURSOR&&(this.$cursorLayer.update(n),this.$moveTextAreaToCursor()),e&(this.CHANGE_MARKER|this.CHANGE_MARKER_FRONT)&&this.$markerFront.update(n),e&(this.CHANGE_MARKER|this.CHANGE_MARKER_BACK)&&this.$markerBack.update(n),this._signal("afterRender",e)},e.prototype.$autosize=function(){var e=this.session.getScreenLength()*this.lineHeight,t=this.$maxLines*this.lineHeight,n=Math.min(t,Math.max((this.$minLines||1)*this.lineHeight,e))+this.scrollMargin.v+(this.$extraHeight||0);this.$horizScroll&&(n+=this.scrollBarH.getHeight()),this.$maxPixelHeight&&n>this.$maxPixelHeight&&(n=this.$maxPixelHeight);var r=n<=2*this.lineHeight,i=!r&&e>t;if(n!=this.desiredHeight||this.$size.height!=this.desiredHeight||i!=this.$vScroll){i!=this.$vScroll&&(this.$vScroll=i,this.scrollBarV.setVisible(i));var s=this.container.clientWidth;this.container.style.height=n+"px",this.$updateCachedSize(!0,this.$gutterWidth,s,n),this.desiredHeight=n,this._signal("autosize")}},e.prototype.$computeLayerConfig=function(){var e=this.session,t=this.$size,n=t.height<=2*this.lineHeight,r=this.session.getScreenLength(),i=r*this.lineHeight,s=this.$getLongestLine(),o=!n&&(this.$hScrollBarAlwaysVisible||t.scrollerWidth-s-2*this.$padding<0),u=this.$horizScroll!==o;u&&(this.$horizScroll=o,this.scrollBarH.setVisible(o));var a=this.$vScroll;this.$maxLines&&this.lineHeight>1&&this.$autosize();var f=t.scrollerHeight+this.lineHeight,l=!this.$maxLines&&this.$scrollPastEnd?(t.scrollerHeight-this.lineHeight)*this.$scrollPastEnd:0;i+=l;var c=this.scrollMargin;this.session.setScrollTop(Math.max(-c.top,Math.min(this.scrollTop,i-t.scrollerHeight+c.bottom))),this.session.setScrollLeft(Math.max(-c.left,Math.min(this.scrollLeft,s+2*this.$padding-t.scrollerWidth+c.right)));var h=!n&&(this.$vScrollBarAlwaysVisible||t.scrollerHeight-i+l<0||this.scrollTop>c.top),p=a!==h;p&&(this.$vScroll=h,this.scrollBarV.setVisible(h));var d=this.scrollTop%this.lineHeight,v=Math.ceil(f/this.lineHeight)-1,m=Math.max(0,Math.round((this.scrollTop-d)/this.lineHeight)),g=m+v,y,b,w=this.lineHeight;m=e.screenToDocumentRow(m,0);var E=e.getFoldLine(m);E&&(m=E.start.row),y=e.documentToScreenRow(m,0),b=e.getRowLength(m)*w,g=Math.min(e.screenToDocumentRow(g,0),e.getLength()-1),f=t.scrollerHeight+e.getRowLength(g)*w+b,d=this.scrollTop-y*w;var S=0;if(this.layerConfig.width!=s||u)S=this.CHANGE_H_SCROLL;if(u||p)S|=this.$updateCachedSize(!0,this.gutterWidth,t.width,t.height),this._signal("scrollbarVisibilityChanged"),p&&(s=this.$getLongestLine());return this.layerConfig={width:s,padding:this.$padding,firstRow:m,firstRowScreen:y,lastRow:g,lineHeight:w,characterWidth:this.characterWidth,minHeight:f,maxHeight:i,offset:d,gutterOffset:w?Math.max(0,Math.ceil((d+t.height-t.scrollerHeight)/w)):0,height:this.$size.scrollerHeight},this.session.$bidiHandler&&this.session.$bidiHandler.setContentWidth(s-this.$padding),S},e.prototype.$updateLines=function(){if(!this.$changedLines)return;var e=this.$changedLines.firstRow,t=this.$changedLines.lastRow;this.$changedLines=null;var n=this.layerConfig;if(e>n.lastRow+1)return;if(tthis.$textLayer.MAX_LINE_LENGTH&&(e=this.$textLayer.MAX_LINE_LENGTH+30),Math.max(this.$size.scrollerWidth-2*this.$padding,Math.round(e*this.characterWidth))},e.prototype.updateFrontMarkers=function(){this.$markerFront.setMarkers(this.session.getMarkers(!0)),this.$loop.schedule(this.CHANGE_MARKER_FRONT)},e.prototype.updateBackMarkers=function(){this.$markerBack.setMarkers(this.session.getMarkers()),this.$loop.schedule(this.CHANGE_MARKER_BACK)},e.prototype.addGutterDecoration=function(e,t){this.$gutterLayer.addGutterDecoration(e,t)},e.prototype.removeGutterDecoration=function(e,t){this.$gutterLayer.removeGutterDecoration(e,t)},e.prototype.updateBreakpoints=function(e){this._rows=e,this.$loop.schedule(this.CHANGE_GUTTER)},e.prototype.setAnnotations=function(e){this.$gutterLayer.setAnnotations(e),this.$loop.schedule(this.CHANGE_GUTTER)},e.prototype.updateCursor=function(){this.$loop.schedule(this.CHANGE_CURSOR)},e.prototype.hideCursor=function(){this.$cursorLayer.hideCursor()},e.prototype.showCursor=function(){this.$cursorLayer.showCursor()},e.prototype.scrollSelectionIntoView=function(e,t,n){this.scrollCursorIntoView(e,n),this.scrollCursorIntoView(t,n)},e.prototype.scrollCursorIntoView=function(e,t,n){if(this.$size.scrollerHeight===0)return;var r=this.$cursorLayer.getPixelPosition(e),i=r.left,s=r.top,o=n&&n.top||0,u=n&&n.bottom||0;this.$scrollAnimation&&(this.$stopAnimation=!0);var a=this.$scrollAnimation?this.session.getScrollTop():this.scrollTop;a+o>s?(t&&a+o>s+this.lineHeight&&(s-=t*this.$size.scrollerHeight),s===0&&(s=-this.scrollMargin.top),this.session.setScrollTop(s)):a+this.$size.scrollerHeight-u=1-this.scrollMargin.top)return!0;if(t>0&&this.session.getScrollTop()+this.$size.scrollerHeight-this.layerConfig.maxHeight<-1+this.scrollMargin.bottom)return!0;if(e<0&&this.session.getScrollLeft()>=1-this.scrollMargin.left)return!0;if(e>0&&this.session.getScrollLeft()+this.$size.scrollerWidth-this.layerConfig.width<-1+this.scrollMargin.right)return!0},e.prototype.pixelToScreenCoordinates=function(e,t){var n;if(this.$hasCssTransforms){n={top:0,left:0};var r=this.$fontMetrics.transformCoordinates([e,t]);e=r[1]-this.gutterWidth-this.margin.left,t=r[0]}else n=this.scroller.getBoundingClientRect();var i=e+this.scrollLeft-n.left-this.$padding,s=i/this.characterWidth,o=Math.floor((t+this.scrollTop-n.top)/this.lineHeight),u=this.$blockCursor?Math.floor(s):Math.round(s);return{row:o,column:u,side:s-u>0?1:-1,offsetX:i}},e.prototype.screenToTextCoordinates=function(e,t){var n;if(this.$hasCssTransforms){n={top:0,left:0};var r=this.$fontMetrics.transformCoordinates([e,t]);e=r[1]-this.gutterWidth-this.margin.left,t=r[0]}else n=this.scroller.getBoundingClientRect();var i=e+this.scrollLeft-n.left-this.$padding,s=i/this.characterWidth,o=this.$blockCursor?Math.floor(s):Math.round(s),u=Math.floor((t+this.scrollTop-n.top)/this.lineHeight);return this.session.screenToDocumentPosition(u,Math.max(o,0),i)},e.prototype.textToScreenCoordinates=function(e,t){var n=this.scroller.getBoundingClientRect(),r=this.session.documentToScreenPosition(e,t),i=this.$padding+(this.session.$bidiHandler.isBidiRow(r.row,e)?this.session.$bidiHandler.getPosLeft(r.column):Math.round(r.column*this.characterWidth)),s=r.row*this.lineHeight;return{pageX:n.left+i-this.scrollLeft,pageY:n.top+s-this.scrollTop}},e.prototype.visualizeFocus=function(){i.addCssClass(this.container,"ace_focus")},e.prototype.visualizeBlur=function(){i.removeCssClass(this.container,"ace_focus")},e.prototype.showComposition=function(e){this.$composition=e,e.cssText||(e.cssText=this.textarea.style.cssText),e.useTextareaForIME==undefined&&(e.useTextareaForIME=this.$useTextareaForIME),this.$useTextareaForIME?(i.addCssClass(this.textarea,"ace_composition"),this.textarea.style.cssText="",this.$moveTextAreaToCursor(),this.$cursorLayer.element.style.display="none"):e.markerId=this.session.addMarker(e.markerRange,"ace_composition_marker","text")},e.prototype.setCompositionText=function(e){var t=this.session.selection.cursor;this.addToken(e,"composition_placeholder",t.row,t.column),this.$moveTextAreaToCursor()},e.prototype.hideComposition=function(){if(!this.$composition)return;this.$composition.markerId&&this.session.removeMarker(this.$composition.markerId),i.removeCssClass(this.textarea,"ace_composition"),this.textarea.style.cssText=this.$composition.cssText;var e=this.session.selection.cursor;this.removeExtraToken(e.row,e.column),this.$composition=null,this.$cursorLayer.element.style.display=""},e.prototype.setGhostText=function(e,t){var n=this.session.selection.cursor,r=t||{row:n.row,column:n.column};this.removeGhostText();var i=e.split("\n");this.addToken(i[0],"ghost_text",r.row,r.column),this.$ghostText={text:e,position:{row:r.row,column:r.column}};if(i.length>1){this.$ghostTextWidget={text:i.slice(1).join("\n"),row:r.row,column:r.column,className:"ace_ghost_text"},this.session.widgetManager.addLineWidget(this.$ghostTextWidget);var s=this.$cursorLayer.getPixelPosition(r,!0),o=this.container,u=o.getBoundingClientRect().height,a=i.length*this.lineHeight,f=a1||Math.abs(e.$size.height-r)>1?e.$resizeTimer.delay():e.$resizeTimer.cancel()}),this.$resizeObserver.observe(this.container)},e}();E.prototype.CHANGE_CURSOR=1,E.prototype.CHANGE_MARKER=2,E.prototype.CHANGE_GUTTER=4,E.prototype.CHANGE_SCROLL=8,E.prototype.CHANGE_LINES=16,E.prototype.CHANGE_TEXT=32,E.prototype.CHANGE_SIZE=64,E.prototype.CHANGE_MARKER_BACK=128,E.prototype.CHANGE_MARKER_FRONT=256,E.prototype.CHANGE_FULL=512,E.prototype.CHANGE_H_SCROLL=1024,E.prototype.$changes=0,E.prototype.$padding=null,E.prototype.$frozen=!1,E.prototype.STEPS=8,r.implement(E.prototype,g),o.defineOptions(E.prototype,"renderer",{useResizeObserver:{set:function(e){!e&&this.$resizeObserver?(this.$resizeObserver.disconnect(),this.$resizeTimer.cancel(),this.$resizeTimer=this.$resizeObserver=null):e&&!this.$resizeObserver&&this.$addResizeObserver()}},animatedScroll:{initialValue:!1},showInvisibles:{set:function(e){this.$textLayer.setShowInvisibles(e)&&this.$loop.schedule(this.CHANGE_TEXT)},initialValue:!1},showPrintMargin:{set:function(){this.$updatePrintMargin()},initialValue:!0},printMarginColumn:{set:function(){this.$updatePrintMargin()},initialValue:80},printMargin:{set:function(e){typeof e=="number"&&(this.$printMarginColumn=e),this.$showPrintMargin=!!e,this.$updatePrintMargin()},get:function(){return this.$showPrintMargin&&this.$printMarginColumn}},showGutter:{set:function(e){this.$gutter.style.display=e?"block":"none",this.$loop.schedule(this.CHANGE_FULL),this.onGutterResize()},initialValue:!0},useSvgGutterIcons:{set:function(e){this.$gutterLayer.$useSvgGutterIcons=e},initialValue:!1},showFoldedAnnotations:{set:function(e){this.$gutterLayer.$showFoldedAnnotations=e},initialValue:!1},fadeFoldWidgets:{set:function(e){i.setCssClass(this.$gutter,"ace_fade-fold-widgets",e)},initialValue:!1},showFoldWidgets:{set:function(e){this.$gutterLayer.setShowFoldWidgets(e),this.$loop.schedule(this.CHANGE_GUTTER)},initialValue:!0},displayIndentGuides:{set:function(e){this.$textLayer.setDisplayIndentGuides(e)&&this.$loop.schedule(this.CHANGE_TEXT)},initialValue:!0},highlightIndentGuides:{set:function(e){this.$textLayer.setHighlightIndentGuides(e)==1?this.$textLayer.$highlightIndentGuide():this.$textLayer.$clearActiveIndentGuide(this.$textLayer.$lines.cells)},initialValue:!0},highlightGutterLine:{set:function(e){this.$gutterLayer.setHighlightGutterLine(e),this.$loop.schedule(this.CHANGE_GUTTER)},initialValue:!0},hScrollBarAlwaysVisible:{set:function(e){(!this.$hScrollBarAlwaysVisible||!this.$horizScroll)&&this.$loop.schedule(this.CHANGE_SCROLL)},initialValue:!1},vScrollBarAlwaysVisible:{set:function(e){(!this.$vScrollBarAlwaysVisible||!this.$vScroll)&&this.$loop.schedule(this.CHANGE_SCROLL)},initialValue:!1},fontSize:{set:function(e){typeof e=="number"&&(e+="px"),this.container.style.fontSize=e,this.updateFontSize()},initialValue:12},fontFamily:{set:function(e){this.container.style.fontFamily=e,this.updateFontSize()}},maxLines:{set:function(e){this.updateFull()}},minLines:{set:function(e){this.$minLines<562949953421311||(this.$minLines=0),this.updateFull()}},maxPixelHeight:{set:function(e){this.updateFull()},initialValue:0},scrollPastEnd:{set:function(e){e=+e||0;if(this.$scrollPastEnd==e)return;this.$scrollPastEnd=e,this.$loop.schedule(this.CHANGE_SCROLL)},initialValue:0,handlesSet:!0},fixedWidthGutter:{set:function(e){this.$gutterLayer.$fixedWidth=!!e,this.$loop.schedule(this.CHANGE_GUTTER)}},customScrollbar:{set:function(e){this.$updateCustomScrollbar(e)},initialValue:!1},theme:{set:function(e){this.setTheme(e)},get:function(){return this.$themeId||this.theme},initialValue:"./theme/textmate",handlesSet:!0},hasCssTransforms:{},useTextareaForIME:{initialValue:!w.isMobile&&!w.isIE}}),t.VirtualRenderer=E}),define("ace/worker/worker_client",["require","exports","module","ace/lib/oop","ace/lib/net","ace/lib/event_emitter","ace/config"],function(e,t,n){"use strict";function u(e){var t="importScripts('"+i.qualifyURL(e)+"');";try{return new Blob([t],{type:"application/javascript"})}catch(n){var r=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder,s=new r;return s.append(t),s.getBlob("application/javascript")}}function a(e){if(typeof Worker=="undefined")return{postMessage:function(){},terminate:function(){}};if(o.get("loadWorkerFromBlob")){var t=u(e),n=window.URL||window.webkitURL,r=n.createObjectURL(t);return new Worker(r)}return new Worker(e)}var r=e("../lib/oop"),i=e("../lib/net"),s=e("../lib/event_emitter").EventEmitter,o=e("../config"),f=function(e){e.postMessage||(e=this.$createWorkerFromOldConfig.apply(this,arguments)),this.$worker=e,this.$sendDeltaQueue=this.$sendDeltaQueue.bind(this),this.changeListener=this.changeListener.bind(this),this.onMessage=this.onMessage.bind(this),this.callbackId=1,this.callbacks={},this.$worker.onmessage=this.onMessage};(function(){r.implement(this,s),this.$createWorkerFromOldConfig=function(t,n,r,i,s){e.nameToUrl&&!e.toUrl&&(e.toUrl=e.nameToUrl);if(o.get("packaged")||!e.toUrl)i=i||o.moduleUrl(n,"worker");else{var u=this.$normalizePath;i=i||u(e.toUrl("ace/worker/worker.js",null,"_"));var f={};t.forEach(function(t){f[t]=u(e.toUrl(t,null,"_").replace(/(\.js)?(\?.*)?$/,""))})}return this.$worker=a(i),s&&this.send("importScripts",s),this.$worker.postMessage({init:!0,tlns:f,module:n,classname:r}),this.$worker},this.onMessage=function(e){var t=e.data;switch(t.type){case"event":this._signal(t.name,{data:t.data});break;case"call":var n=this.callbacks[t.id];n&&(n(t.data),delete this.callbacks[t.id]);break;case"error":this.reportError(t.data);break;case"log":window.console&&console.log&&console.log.apply(console,t.data)}},this.reportError=function(e){window.console&&console.error&&console.error(e)},this.$normalizePath=function(e){return i.qualifyURL(e)},this.terminate=function(){this._signal("terminate",{}),this.deltaQueue=null,this.$worker.terminate(),this.$worker.onerror=function(e){e.preventDefault()},this.$worker=null,this.$doc&&this.$doc.off("change",this.changeListener),this.$doc=null},this.send=function(e,t){this.$worker.postMessage({command:e,args:t})},this.call=function(e,t,n){if(n){var r=this.callbackId++;this.callbacks[r]=n,t.push(r)}this.send(e,t)},this.emit=function(e,t){try{t.data&&t.data.err&&(t.data.err={message:t.data.err.message,stack:t.data.err.stack,code:t.data.err.code}),this.$worker&&this.$worker.postMessage({event:e,data:{data:t.data}})}catch(n){console.error(n.stack)}},this.attachToDocument=function(e){this.$doc&&this.terminate(),this.$doc=e,this.call("setValue",[e.getValue()]),e.on("change",this.changeListener,!0)},this.changeListener=function(e){this.deltaQueue||(this.deltaQueue=[],setTimeout(this.$sendDeltaQueue,0)),e.action=="insert"?this.deltaQueue.push(e.start,e.lines):this.deltaQueue.push(e.start,e.end)},this.$sendDeltaQueue=function(){var e=this.deltaQueue;if(!e)return;this.deltaQueue=null,e.length>50&&e.length>this.$doc.getLength()>>1?this.call("setValue",[this.$doc.getValue()]):this.emit("change",{data:e})}}).call(f.prototype);var l=function(e,t,n){var r=null,i=!1,u=Object.create(s),a=[],l=new f({messageBuffer:a,terminate:function(){},postMessage:function(e){a.push(e);if(!r)return;i?setTimeout(c):c()}});l.setEmitSync=function(e){i=e};var c=function(){var e=a.shift();e.command?r[e.command].apply(r,e.args):e.event&&u._signal(e.event,e.data)};return u.postMessage=function(e){l.onMessage({data:e})},u.callback=function(e,t){this.postMessage({type:"call",id:t,data:e})},u.emit=function(e,t){this.postMessage({type:"event",name:e,data:t})},o.loadModule(["worker",t],function(e){r=new e[n](u);while(a.length)c()}),l};t.UIWorkerClient=l,t.WorkerClient=f,t.createWorker=a}),define("ace/placeholder",["require","exports","module","ace/range","ace/lib/event_emitter","ace/lib/oop"],function(e,t,n){"use strict";var r=e("./range").Range,i=e("./lib/event_emitter").EventEmitter,s=e("./lib/oop"),o=function(){function e(e,t,n,r,i,s){var o=this;this.length=t,this.session=e,this.doc=e.getDocument(),this.mainClass=i,this.othersClass=s,this.$onUpdate=this.onUpdate.bind(this),this.doc.on("change",this.$onUpdate,!0),this.$others=r,this.$onCursorChange=function(){setTimeout(function(){o.onCursorChange()})},this.$pos=n;var u=e.getUndoManager().$undoStack||e.getUndoManager().$undostack||{length:-1};this.$undoStackDepth=u.length,this.setup(),e.selection.on("changeCursor",this.$onCursorChange)}return e.prototype.setup=function(){var e=this,t=this.doc,n=this.session;this.selectionBefore=n.selection.toJSON(),n.selection.inMultiSelectMode&&n.selection.toSingleRange(),this.pos=t.createAnchor(this.$pos.row,this.$pos.column);var i=this.pos;i.$insertRight=!0,i.detach(),i.markerId=n.addMarker(new r(i.row,i.column,i.row,i.column+this.length),this.mainClass,null,!1),this.others=[],this.$others.forEach(function(n){var r=t.createAnchor(n.row,n.column);r.$insertRight=!0,r.detach(),e.others.push(r)}),n.setUndoSelect(!1)},e.prototype.showOtherMarkers=function(){if(this.othersActive)return;var e=this.session,t=this;this.othersActive=!0,this.others.forEach(function(n){n.markerId=e.addMarker(new r(n.row,n.column,n.row,n.column+t.length),t.othersClass,null,!1)})},e.prototype.hideOtherMarkers=function(){if(!this.othersActive)return;this.othersActive=!1;for(var e=0;e=this.pos.column&&t.start.column<=this.pos.column+this.length+1,s=t.start.column-this.pos.column;this.updateAnchors(e),i&&(this.length+=n);if(i&&!this.session.$fromUndo)if(e.action==="insert")for(var o=this.others.length-1;o>=0;o--){var u=this.others[o],a={row:u.row,column:u.column+s};this.doc.insertMergedLines(a,e.lines)}else if(e.action==="remove")for(var o=this.others.length-1;o>=0;o--){var u=this.others[o],a={row:u.row,column:u.column+s};this.doc.remove(new r(a.row,a.column,a.row,a.column-n))}this.$updating=!1,this.updateMarkers()},e.prototype.updateAnchors=function(e){this.pos.onChange(e);for(var t=this.others.length;t--;)this.others[t].onChange(e);this.updateMarkers()},e.prototype.updateMarkers=function(){if(this.$updating)return;var e=this,t=this.session,n=function(n,i){t.removeMarker(n.markerId),n.markerId=t.addMarker(new r(n.row,n.column,n.row,n.column+e.length),i,null,!1)};n(this.pos,this.mainClass);for(var i=this.others.length;i--;)n(this.others[i],this.othersClass)},e.prototype.onCursorChange=function(e){if(this.$updating||!this.session)return;var t=this.session.selection.getCursor();t.row===this.pos.row&&t.column>=this.pos.column&&t.column<=this.pos.column+this.length?(this.showOtherMarkers(),this._emit("cursorEnter",e)):(this.hideOtherMarkers(),this._emit("cursorLeave",e))},e.prototype.detach=function(){this.session.removeMarker(this.pos&&this.pos.markerId),this.hideOtherMarkers(),this.doc.off("change",this.$onUpdate),this.session.selection.off("changeCursor",this.$onCursorChange),this.session.setUndoSelect(!0),this.session=null},e.prototype.cancel=function(){if(this.$undoStackDepth===-1)return;var e=this.session.getUndoManager(),t=(e.$undoStack||e.$undostack).length-this.$undoStackDepth;for(var n=0;n1?e.multiSelect.joinSelections():e.multiSelect.splitIntoLines()},bindKey:{win:"Ctrl-Alt-L",mac:"Ctrl-Alt-L"},readOnly:!0},{name:"splitSelectionIntoLines",description:"Split into lines",exec:function(e){e.multiSelect.splitIntoLines()},readOnly:!0},{name:"alignCursors",description:"Align cursors",exec:function(e){e.alignCursors()},bindKey:{win:"Ctrl-Alt-A",mac:"Ctrl-Alt-A"},scrollIntoView:"cursor"},{name:"findAll",description:"Find all",exec:function(e){e.findAll()},bindKey:{win:"Ctrl-Alt-K",mac:"Ctrl-Alt-G"},scrollIntoView:"cursor",readOnly:!0}],t.multiSelectCommands=[{name:"singleSelection",description:"Single selection",bindKey:"esc",exec:function(e){e.exitMultiSelectMode()},scrollIntoView:"cursor",readOnly:!0,isAvailable:function(e){return e&&e.inMultiSelectMode}}];var r=e("../keyboard/hash_handler").HashHandler;t.keyboardHandler=new r(t.multiSelectCommands)}),define("ace/multi_select",["require","exports","module","ace/range_list","ace/range","ace/selection","ace/mouse/multi_select_handler","ace/lib/event","ace/lib/lang","ace/commands/multi_select_commands","ace/search","ace/edit_session","ace/editor","ace/config"],function(e,t,n){function h(e,t,n){return c.$options.wrap=!0,c.$options.needle=t,c.$options.backwards=n==-1,c.find(e)}function v(e,t){return e.row==t.row&&e.column==t.column}function m(e){if(e.$multiselectOnSessionChange)return;e.$onAddRange=e.$onAddRange.bind(e),e.$onRemoveRange=e.$onRemoveRange.bind(e),e.$onMultiSelect=e.$onMultiSelect.bind(e),e.$onSingleSelect=e.$onSingleSelect.bind(e),e.$multiselectOnSessionChange=t.onSessionChange.bind(e),e.$checkMultiselectChange=e.$checkMultiselectChange.bind(e),e.$multiselectOnSessionChange(e),e.on("changeSession",e.$multiselectOnSessionChange),e.on("mousedown",o),e.commands.addCommands(f.defaultCommands),g(e)}function g(e){function r(t){n&&(e.renderer.setMouseCursor(""),n=!1)}if(!e.textInput)return;var t=e.textInput.getElement(),n=!1;u.addListener(t,"keydown",function(t){var i=t.keyCode==18&&!(t.ctrlKey||t.shiftKey||t.metaKey);e.$blockSelectEnabled&&i?n||(e.renderer.setMouseCursor("crosshair"),n=!0):n&&r()},e),u.addListener(t,"keyup",r,e),u.addListener(t,"blur",r,e)}var r=e("./range_list").RangeList,i=e("./range").Range,s=e("./selection").Selection,o=e("./mouse/multi_select_handler").onMouseDown,u=e("./lib/event"),a=e("./lib/lang"),f=e("./commands/multi_select_commands");t.commands=f.defaultCommands.concat(f.multiSelectCommands);var l=e("./search").Search,c=new l,p=e("./edit_session").EditSession;(function(){this.getSelectionMarkers=function(){return this.$selectionMarkers}}).call(p.prototype),function(){this.ranges=null,this.rangeList=null,this.addRange=function(e,t){if(!e)return;if(!this.inMultiSelectMode&&this.rangeCount===0){var n=this.toOrientedRange();this.rangeList.add(n),this.rangeList.add(e);if(this.rangeList.ranges.length!=2)return this.rangeList.removeAll(),t||this.fromOrientedRange(e);this.rangeList.removeAll(),this.rangeList.add(n),this.$onAddRange(n)}e.cursor||(e.cursor=e.end);var r=this.rangeList.add(e);return this.$onAddRange(e),r.length&&this.$onRemoveRange(r),this.rangeCount>1&&!this.inMultiSelectMode&&(this._signal("multiSelect"),this.inMultiSelectMode=!0,this.session.$undoSelect=!1,this.rangeList.attach(this.session)),t||this.fromOrientedRange(e)},this.toSingleRange=function(e){e=e||this.ranges[0];var t=this.rangeList.removeAll();t.length&&this.$onRemoveRange(t),e&&this.fromOrientedRange(e)},this.substractPoint=function(e){var t=this.rangeList.substractPoint(e);if(t)return this.$onRemoveRange(t),t[0]},this.mergeOverlappingRanges=function(){var e=this.rangeList.merge();e.length&&this.$onRemoveRange(e)},this.$onAddRange=function(e){this.rangeCount=this.rangeList.ranges.length,this.ranges.unshift(e),this._signal("addRange",{range:e})},this.$onRemoveRange=function(e){this.rangeCount=this.rangeList.ranges.length;if(this.rangeCount==1&&this.inMultiSelectMode){var t=this.rangeList.ranges.pop();e.push(t),this.rangeCount=0}for(var n=e.length;n--;){var r=this.ranges.indexOf(e[n]);this.ranges.splice(r,1)}this._signal("removeRange",{ranges:e}),this.rangeCount===0&&this.inMultiSelectMode&&(this.inMultiSelectMode=!1,this._signal("singleSelect"),this.session.$undoSelect=!0,this.rangeList.detach(this.session)),t=t||this.ranges[0],t&&!t.isEqual(this.getRange())&&this.fromOrientedRange(t)},this.$initRangeList=function(){if(this.rangeList)return;this.rangeList=new r,this.ranges=[],this.rangeCount=0},this.getAllRanges=function(){return this.rangeCount?this.rangeList.ranges.concat():[this.getRange()]},this.splitIntoLines=function(){var e=this.ranges.length?this.ranges:[this.getRange()],t=[];for(var n=0;n1){var e=this.rangeList.ranges,t=e[e.length-1],n=i.fromPoints(e[0].start,t.end);this.toSingleRange(),this.setSelectionRange(n,t.cursor==t.start)}else{var r=this.session.documentToScreenPosition(this.cursor),s=this.session.documentToScreenPosition(this.anchor),o=this.rectangularRangeBlock(r,s);o.forEach(this.addRange,this)}},this.rectangularRangeBlock=function(e,t,n){var r=[],s=e.column0)g--;if(g>0){var y=0;while(r[y].isEmpty())y++}for(var b=g;b>=y;b--)r[b].isEmpty()&&r.splice(b,1)}return r}}.call(s.prototype);var d=e("./editor").Editor;(function(){this.updateSelectionMarkers=function(){this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.addSelectionMarker=function(e){e.cursor||(e.cursor=e.end);var t=this.getSelectionStyle();return e.marker=this.session.addMarker(e,"ace_selection",t),this.session.$selectionMarkers.push(e),this.session.selectionMarkerCount=this.session.$selectionMarkers.length,e},this.removeSelectionMarker=function(e){if(!e.marker)return;this.session.removeMarker(e.marker);var t=this.session.$selectionMarkers.indexOf(e);t!=-1&&this.session.$selectionMarkers.splice(t,1),this.session.selectionMarkerCount=this.session.$selectionMarkers.length},this.removeSelectionMarkers=function(e){var t=this.session.$selectionMarkers;for(var n=e.length;n--;){var r=e[n];if(!r.marker)continue;this.session.removeMarker(r.marker);var i=t.indexOf(r);i!=-1&&t.splice(i,1)}this.session.selectionMarkerCount=t.length},this.$onAddRange=function(e){this.addSelectionMarker(e.range),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onRemoveRange=function(e){this.removeSelectionMarkers(e.ranges),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onMultiSelect=function(e){if(this.inMultiSelectMode)return;this.inMultiSelectMode=!0,this.setStyle("ace_multiselect"),this.keyBinding.addKeyboardHandler(f.keyboardHandler),this.commands.setDefaultHandler("exec",this.$onMultiSelectExec),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onSingleSelect=function(e){if(this.session.multiSelect.inVirtualMode)return;this.inMultiSelectMode=!1,this.unsetStyle("ace_multiselect"),this.keyBinding.removeKeyboardHandler(f.keyboardHandler),this.commands.removeDefaultHandler("exec",this.$onMultiSelectExec),this.renderer.updateCursor(),this.renderer.updateBackMarkers(),this._emit("changeSelection")},this.$onMultiSelectExec=function(e){var t=e.command,n=e.editor;if(!n.multiSelect)return;if(!t.multiSelectAction){var r=t.exec(n,e.args||{});n.multiSelect.addRange(n.multiSelect.toOrientedRange()),n.multiSelect.mergeOverlappingRanges()}else t.multiSelectAction=="forEach"?r=n.forEachSelection(t,e.args):t.multiSelectAction=="forEachLine"?r=n.forEachSelection(t,e.args,!0):t.multiSelectAction=="single"?(n.exitMultiSelectMode(),r=t.exec(n,e.args||{})):r=t.multiSelectAction(n,e.args||{});return r},this.forEachSelection=function(e,t,n){if(this.inVirtualSelectionMode)return;var r=n&&n.keepOrder,i=n==1||n&&n.$byLines,o=this.session,u=this.selection,a=u.rangeList,f=(r?u:a).ranges,l;if(!f.length)return e.exec?e.exec(this,t||{}):e(this,t||{});var c=u._eventRegistry;u._eventRegistry={};var h=new s(o);this.inVirtualSelectionMode=!0;for(var p=f.length;p--;){if(i)while(p>0&&f[p].start.row==f[p-1].end.row)p--;h.fromOrientedRange(f[p]),h.index=p,this.selection=o.selection=h;var d=e.exec?e.exec(this,t||{}):e(this,t||{});!l&&d!==undefined&&(l=d),h.toOrientedRange(f[p])}h.detach(),this.selection=o.selection=u,this.inVirtualSelectionMode=!1,u._eventRegistry=c,u.mergeOverlappingRanges(),u.ranges[0]&&u.fromOrientedRange(u.ranges[0]);var v=this.renderer.$scrollAnimation;return this.onCursorChange(),this.onSelectionChange(),v&&v.from==v.to&&this.renderer.animateScrolling(v.from),l},this.exitMultiSelectMode=function(){if(!this.inMultiSelectMode||this.inVirtualSelectionMode)return;this.multiSelect.toSingleRange()},this.getSelectedText=function(){var e="";if(this.inMultiSelectMode&&!this.inVirtualSelectionMode){var t=this.multiSelect.rangeList.ranges,n=[];for(var r=0;r0);u<0&&(u=0),f>=c&&(f=c-1)}var p=this.session.removeFullLines(u,f);p=this.$reAlignText(p,l),this.session.insert({row:u,column:0},p.join("\n")+"\n"),l||(o.start.column=0,o.end.column=p[p.length-1].length),this.selection.setRange(o)}else{s.forEach(function(e){t.substractPoint(e.cursor)});var d=0,v=Infinity,m=n.map(function(t){var n=t.cursor,r=e.getLine(n.row),i=r.substr(n.column).search(/\S/g);return i==-1&&(i=0),n.column>d&&(d=n.column),io?e.insert(r,a.stringRepeat(" ",s-o)):e.remove(new i(r.row,r.column,r.row,r.column-s+o)),t.start.column=t.end.column=d,t.start.row=t.end.row=r.row,t.cursor=t.end}),t.fromOrientedRange(n[0]),this.renderer.updateCursor(),this.renderer.updateBackMarkers()}},this.$reAlignText=function(e,t){function u(e){return a.stringRepeat(" ",e)}function f(e){return e[2]?u(i)+e[2]+u(s-e[2].length+o)+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}function l(e){return e[2]?u(i+s-e[2].length)+e[2]+u(o)+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}function c(e){return e[2]?u(i)+e[2]+u(o)+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}var n=!0,r=!0,i,s,o;return e.map(function(e){var t=e.match(/(\s*)(.*?)(\s*)([=:].*)/);return t?i==null?(i=t[1].length,s=t[2].length,o=t[3].length,t):(i+s+o!=t[1].length+t[2].length+t[3].length&&(r=!1),i!=t[1].length&&(n=!1),i>t[1].length&&(i=t[1].length),st[3].length&&(o=t[3].length),t):[e]}).map(t?f:n?r?l:f:c)}}).call(d.prototype),t.onSessionChange=function(e){var t=e.session;t&&!t.multiSelect&&(t.$selectionMarkers=[],t.selection.$initRangeList(),t.multiSelect=t.selection),this.multiSelect=t&&t.multiSelect;var n=e.oldSession;n&&(n.multiSelect.off("addRange",this.$onAddRange),n.multiSelect.off("removeRange",this.$onRemoveRange),n.multiSelect.off("multiSelect",this.$onMultiSelect),n.multiSelect.off("singleSelect",this.$onSingleSelect),n.multiSelect.lead.off("change",this.$checkMultiselectChange),n.multiSelect.anchor.off("change",this.$checkMultiselectChange)),t&&(t.multiSelect.on("addRange",this.$onAddRange),t.multiSelect.on("removeRange",this.$onRemoveRange),t.multiSelect.on("multiSelect",this.$onMultiSelect),t.multiSelect.on("singleSelect",this.$onSingleSelect),t.multiSelect.lead.on("change",this.$checkMultiselectChange),t.multiSelect.anchor.on("change",this.$checkMultiselectChange)),t&&this.inMultiSelectMode!=t.selection.inMultiSelectMode&&(t.selection.inMultiSelectMode?this.$onMultiSelect():this.$onSingleSelect())},t.MultiSelect=m,e("./config").defineOptions(d.prototype,"editor",{enableMultiselect:{set:function(e){m(this),e?this.on("mousedown",o):this.off("mousedown",o)},value:!0},enableBlockSelect:{set:function(e){this.$blockSelectEnabled=e},value:!0}})}),define("ace/mode/folding/fold_mode",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../../range").Range,i=t.FoldMode=function(){};(function(){this.foldingStartMarker=null,this.foldingStopMarker=null,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);return this.foldingStartMarker.test(r)?"start":t=="markbeginend"&&this.foldingStopMarker&&this.foldingStopMarker.test(r)?"end":""},this.getFoldWidgetRange=function(e,t,n){return null},this.indentationBlock=function(e,t,n){var i=/\S/,s=e.getLine(t),o=s.search(i);if(o==-1)return;var u=n||s.length,a=e.getLength(),f=t,l=t;while(++tf){var p=e.getLine(l).length;return new r(f,u,l,p)}},this.openingBracketBlock=function(e,t,n,i,s){var o={row:n,column:i+1},u=e.$findClosingBracket(t,o,s);if(!u)return;var a=e.foldWidgets[u.row];return a==null&&(a=e.getFoldWidget(u.row)),a=="start"&&u.row>o.row&&(u.row--,u.column=e.getLine(u.row).length),r.fromPoints(o,u)},this.closingBracketBlock=function(e,t,n,i,s){var o={row:n,column:i},u=e.$findOpeningBracket(t,o);if(!u)return;return u.column++,o.column--,r.fromPoints(u,o)}}).call(i.prototype)}),define("ace/ext/error_marker",["require","exports","module","ace/line_widgets","ace/lib/dom","ace/range","ace/config"],function(e,t,n){"use strict";function u(e,t,n){var r=0,i=e.length-1;while(r<=i){var s=r+i>>1,o=n(t,e[s]);if(o>0)r=s+1;else{if(!(o<0))return s;i=s-1}}return-(r+1)}function a(e,t,n){var r=e.getAnnotations().sort(s.comparePoints);if(!r.length)return;var i=u(r,{row:t,column:-1},s.comparePoints);i<0&&(i=-i-1),i>=r.length?i=n>0?0:r.length-1:i===0&&n<0&&(i=r.length-1);var o=r[i];if(!o||!n)return;if(o.row===t){do o=r[i+=n];while(o&&o.row===t);if(!o)return r.slice()}var a=[];t=o.row;do a[n<0?"unshift":"push"](o),o=r[i+=n];while(o&&o.row==t);return a.length&&a}var r=e("../line_widgets").LineWidgets,i=e("../lib/dom"),s=e("../range").Range,o=e("../config").nls;t.showErrorMarker=function(e,t){var n=e.session;n.widgetManager||(n.widgetManager=new r(n),n.widgetManager.attach(e));var s=e.getCursorPosition(),u=s.row,f=n.widgetManager.getWidgetsAtRow(u).filter(function(e){return e.type=="errorMarker"})[0];f?f.destroy():u-=t;var l=a(n,u,t),c;if(l){var h=l[0];s.column=(h.pos&&typeof h.column!="number"?h.pos.sc:h.column)||0,s.row=h.row,c=e.renderer.$gutterLayer.$annotations[s.row]}else{if(f)return;c={text:[o("Looks good!")],className:"ace_ok"}}e.session.unfold(s.row),e.selection.moveToPosition(s);var p={row:s.row,fixedWidth:!0,coverGutter:!0,el:i.createElement("div"),type:"errorMarker"},d=p.el.appendChild(i.createElement("div")),v=p.el.appendChild(i.createElement("div"));v.className="error_widget_arrow "+c.className;var m=e.renderer.$cursorLayer.getPixelPosition(s).left;v.style.left=m+e.renderer.gutterWidth-5+"px",p.el.className="error_widget_wrapper",d.className="error_widget "+c.className,d.innerHTML=c.text.join("
"),d.appendChild(i.createElement("div"));var g=function(e,t,n){if(t===0&&(n==="esc"||n==="return"))return p.destroy(),{command:"null"}};p.destroy=function(){if(e.$mouseHandler.isMousePressed)return;e.keyBinding.removeKeyboardHandler(g),n.widgetManager.removeLineWidget(p),e.off("changeSelection",p.destroy),e.off("changeSession",p.destroy),e.off("mouseup",p.destroy),e.off("change",p.destroy)},e.keyBinding.addKeyboardHandler(g),e.on("changeSelection",p.destroy),e.on("changeSession",p.destroy),e.on("mouseup",p.destroy),e.on("change",p.destroy),e.session.widgetManager.addLineWidget(p),p.el.onmousedown=e.focus.bind(e),e.renderer.scrollCursorIntoView(null,.5,{bottom:p.el.offsetHeight})},i.importCssString("\n .error_widget_wrapper {\n background: inherit;\n color: inherit;\n border:none\n }\n .error_widget {\n border-top: solid 2px;\n border-bottom: solid 2px;\n margin: 5px 0;\n padding: 10px 40px;\n white-space: pre-wrap;\n }\n .error_widget.ace_error, .error_widget_arrow.ace_error{\n border-color: #ff5a5a\n }\n .error_widget.ace_warning, .error_widget_arrow.ace_warning{\n border-color: #F1D817\n }\n .error_widget.ace_info, .error_widget_arrow.ace_info{\n border-color: #5a5a5a\n }\n .error_widget.ace_ok, .error_widget_arrow.ace_ok{\n border-color: #5aaa5a\n }\n .error_widget_arrow {\n position: absolute;\n border: solid 5px;\n border-top-color: transparent!important;\n border-right-color: transparent!important;\n border-left-color: transparent!important;\n top: -5px;\n }\n","error_marker.css",!1)}),define("ace/ace",["require","exports","module","ace/lib/dom","ace/range","ace/editor","ace/edit_session","ace/undomanager","ace/virtual_renderer","ace/worker/worker_client","ace/keyboard/hash_handler","ace/placeholder","ace/multi_select","ace/mode/folding/fold_mode","ace/theme/textmate","ace/ext/error_marker","ace/config","ace/loader_build"],function(e,t,n){"use strict";e("./loader_build")(t);var r=e("./lib/dom"),i=e("./range").Range,s=e("./editor").Editor,o=e("./edit_session").EditSession,u=e("./undomanager").UndoManager,a=e("./virtual_renderer").VirtualRenderer;e("./worker/worker_client"),e("./keyboard/hash_handler"),e("./placeholder"),e("./multi_select"),e("./mode/folding/fold_mode"),e("./theme/textmate"),e("./ext/error_marker"),t.config=e("./config"),t.edit=function(e,n){if(typeof e=="string"){var i=e;e=document.getElementById(i);if(!e)throw new Error("ace.edit can't find div #"+i)}if(e&&e.env&&e.env.editor instanceof s)return e.env.editor;var o="";if(e&&/input|textarea/i.test(e.tagName)){var u=e;o=u.value,e=r.createElement("pre"),u.parentNode.replaceChild(e,u)}else e&&(o=e.textContent,e.innerHTML="");var f=t.createEditSession(o),l=new s(new a(e),f,n),c={document:f,editor:l,onResize:l.resize.bind(l,null)};return u&&(c.textarea=u),l.on("destroy",function(){c.editor.container.env=null}),l.container.env=l.env=c,l},t.createEditSession=function(e,t){var n=new o(e,t);return n.setUndoManager(new u),n},t.Range=i,t.Editor=s,t.EditSession=o,t.UndoManager=u,t.VirtualRenderer=a,t.version=t.config.version}); (function() { + window.require(["ace/ace"], function(a) { + if (a) { + a.config.init(true); + a.define = window.define; + } + var global = (function () { + return this; + })(); + if (!global && typeof window != "undefined") global = window; // can happen in strict mode + if (!global && typeof self != "undefined") global = self; // can happen in webworker + + if (!global.ace) + global.ace = a; + for (var key in a) if (a.hasOwnProperty(key)) + global.ace[key] = a[key]; + global.ace["default"] = global.ace; + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = global.ace; + } + }); + })(); + \ No newline at end of file diff --git a/static/js/ace/ext-language_tools.js b/static/js/ace/ext-language_tools.js new file mode 100644 index 0000000..c15b807 --- /dev/null +++ b/static/js/ace/ext-language_tools.js @@ -0,0 +1,8 @@ +define("ace/snippets",["require","exports","module","ace/lib/dom","ace/lib/oop","ace/lib/event_emitter","ace/lib/lang","ace/range","ace/range_list","ace/keyboard/hash_handler","ace/tokenizer","ace/clipboard","ace/editor"],function(e,t,n){"use strict";function p(e){var t=(new Date).toLocaleString("en-us",e);return t.length==1?"0"+t:t}var r=e("./lib/dom"),i=e("./lib/oop"),s=e("./lib/event_emitter").EventEmitter,o=e("./lib/lang"),u=e("./range").Range,a=e("./range_list").RangeList,f=e("./keyboard/hash_handler").HashHandler,l=e("./tokenizer").Tokenizer,c=e("./clipboard"),h={CURRENT_WORD:function(e){return e.session.getTextRange(e.session.getWordRange())},SELECTION:function(e,t,n){var r=e.session.getTextRange();return n?r.replace(/\n\r?([ \t]*\S)/g,"\n"+n+"$1"):r},CURRENT_LINE:function(e){return e.session.getLine(e.getCursorPosition().row)},PREV_LINE:function(e){return e.session.getLine(e.getCursorPosition().row-1)},LINE_INDEX:function(e){return e.getCursorPosition().row},LINE_NUMBER:function(e){return e.getCursorPosition().row+1},SOFT_TABS:function(e){return e.session.getUseSoftTabs()?"YES":"NO"},TAB_SIZE:function(e){return e.session.getTabSize()},CLIPBOARD:function(e){return c.getText&&c.getText()},FILENAME:function(e){return/[^/\\]*$/.exec(this.FILEPATH(e))[0]},FILENAME_BASE:function(e){return/[^/\\]*$/.exec(this.FILEPATH(e))[0].replace(/\.[^.]*$/,"")},DIRECTORY:function(e){return this.FILEPATH(e).replace(/[^/\\]*$/,"")},FILEPATH:function(e){return"/not implemented.txt"},WORKSPACE_NAME:function(){return"Unknown"},FULLNAME:function(){return"Unknown"},BLOCK_COMMENT_START:function(e){var t=e.session.$mode||{};return t.blockComment&&t.blockComment.start||""},BLOCK_COMMENT_END:function(e){var t=e.session.$mode||{};return t.blockComment&&t.blockComment.end||""},LINE_COMMENT:function(e){var t=e.session.$mode||{};return t.lineCommentStart||""},CURRENT_YEAR:p.bind(null,{year:"numeric"}),CURRENT_YEAR_SHORT:p.bind(null,{year:"2-digit"}),CURRENT_MONTH:p.bind(null,{month:"numeric"}),CURRENT_MONTH_NAME:p.bind(null,{month:"long"}),CURRENT_MONTH_NAME_SHORT:p.bind(null,{month:"short"}),CURRENT_DATE:p.bind(null,{day:"2-digit"}),CURRENT_DAY_NAME:p.bind(null,{weekday:"long"}),CURRENT_DAY_NAME_SHORT:p.bind(null,{weekday:"short"}),CURRENT_HOUR:p.bind(null,{hour:"2-digit",hour12:!1}),CURRENT_MINUTE:p.bind(null,{minute:"2-digit"}),CURRENT_SECOND:p.bind(null,{second:"2-digit"})};h.SELECTED_TEXT=h.SELECTION;var d=function(){function e(){this.snippetMap={},this.snippetNameMap={},this.variables=h}return e.prototype.getTokenizer=function(){return e.$tokenizer||this.createTokenizer()},e.prototype.createTokenizer=function(){function t(e){return e=e.substr(1),/^\d+$/.test(e)?[{tabstopId:parseInt(e,10)}]:[{text:e}]}function n(e){return"(?:[^\\\\"+e+"]|\\\\.)"}var r={regex:"/("+n("/")+"+)/",onMatch:function(e,t,n){var r=n[0];return r.fmtString=!0,r.guard=e.slice(1,-1),r.flag="",""},next:"formatString"};return e.$tokenizer=new l({start:[{regex:/\\./,onMatch:function(e,t,n){var r=e[1];return r=="}"&&n.length?e=r:"`$\\".indexOf(r)!=-1&&(e=r),[e]}},{regex:/}/,onMatch:function(e,t,n){return[n.length?n.shift():e]}},{regex:/\$(?:\d+|\w+)/,onMatch:t},{regex:/\$\{[\dA-Z_a-z]+/,onMatch:function(e,n,r){var i=t(e.substr(1));return r.unshift(i[0]),i},next:"snippetVar"},{regex:/\n/,token:"newline",merge:!1}],snippetVar:[{regex:"\\|"+n("\\|")+"*\\|",onMatch:function(e,t,n){var r=e.slice(1,-1).replace(/\\[,|\\]|,/g,function(e){return e.length==2?e[1]:"\0"}).split("\0").map(function(e){return{value:e}});return n[0].choices=r,[r[0]]},next:"start"},r,{regex:"([^:}\\\\]|\\\\.)*:?",token:"",next:"start"}],formatString:[{regex:/:/,onMatch:function(e,t,n){return n.length&&n[0].expectElse?(n[0].expectElse=!1,n[0].ifEnd={elseEnd:n[0]},[n[0].ifEnd]):":"}},{regex:/\\./,onMatch:function(e,t,n){var r=e[1];return r=="}"&&n.length?e=r:"`$\\".indexOf(r)!=-1?e=r:r=="n"?e="\n":r=="t"?e=" ":"ulULE".indexOf(r)!=-1&&(e={changeCase:r,local:r>"a"}),[e]}},{regex:"/\\w*}",onMatch:function(e,t,n){var r=n.shift();return r&&(r.flag=e.slice(1,-1)),this.next=r&&r.tabstopId?"start":"",[r||e]},next:"start"},{regex:/\$(?:\d+|\w+)/,onMatch:function(e,t,n){return[{text:e.slice(1)}]}},{regex:/\${\w+/,onMatch:function(e,t,n){var r={text:e.slice(2)};return n.unshift(r),[r]},next:"formatStringVar"},{regex:/\n/,token:"newline",merge:!1},{regex:/}/,onMatch:function(e,t,n){var r=n.shift();return this.next=r&&r.tabstopId?"start":"",[r||e]},next:"start"}],formatStringVar:[{regex:/:\/\w+}/,onMatch:function(e,t,n){var r=n[0];return r.formatFunction=e.slice(2,-1),[n.shift()]},next:"formatString"},r,{regex:/:[\?\-+]?/,onMatch:function(e,t,n){e[1]=="+"&&(n[0].ifEnd=n[0]),e[1]=="?"&&(n[0].expectElse=!0)},next:"formatString"},{regex:"([^:}\\\\]|\\\\.)*:?",token:"",next:"formatString"}]}),e.$tokenizer},e.prototype.tokenizeTmSnippet=function(e,t){return this.getTokenizer().getLineTokens(e,t).tokens.map(function(e){return e.value||e})},e.prototype.getVariableValue=function(e,t,n){if(/^\d+$/.test(t))return(this.variables.__||{})[t]||"";if(/^[A-Z]\d+$/.test(t))return(this.variables[t[0]+"__"]||{})[t.substr(1)]||"";t=t.replace(/^TM_/,"");if(!this.variables.hasOwnProperty(t))return"";var r=this.variables[t];return typeof r=="function"&&(r=this.variables[t](e,t,n)),r==null?"":r},e.prototype.tmStrFormat=function(e,t,n){if(!t.fmt)return e;var r=t.flag||"",i=t.guard;i=new RegExp(i,r.replace(/[^gim]/g,""));var s=typeof t.fmt=="string"?this.tokenizeTmSnippet(t.fmt,"formatString"):t.fmt,o=this,u=e.replace(i,function(){var e=o.variables.__;o.variables.__=[].slice.call(arguments);var t=o.resolveVariables(s,n),r="E";for(var i=0;i=0&&s.splice(o,1)}}var n=this.snippetMap,r=this.snippetNameMap;e.content?i(e):Array.isArray(e)&&e.forEach(i)},e.prototype.parseSnippetFile=function(e){e=e.replace(/\r/g,"");var t=[],n={},r=/^#.*|^({[\s\S]*})\s*$|^(\S+) (.*)$|^((?:\n*\t.*)+)/gm,i;while(i=r.exec(e)){if(i[1])try{n=JSON.parse(i[1]),t.push(n)}catch(s){}if(i[4])n.content=i[4].replace(/^\t/gm,""),t.push(n),n={};else{var o=i[2],u=i[3];if(o=="regex"){var a=/\/((?:[^\/\\]|\\.)*)|$/g;n.guard=a.exec(u)[1],n.trigger=a.exec(u)[1],n.endTrigger=a.exec(u)[1],n.endGuard=a.exec(u)[1]}else o=="snippet"?(n.tabTrigger=u.match(/^\S*/)[0],n.name||(n.name=u)):o&&(n[o]=u)}}return t},e.prototype.getSnippetByName=function(e,t){var n=this.snippetNameMap,r;return this.getActiveScopes(t).some(function(t){var i=n[t];return i&&(r=i[e]),!!r},this),r},e}();i.implement(d.prototype,s);var v=function(e,t,n){function l(e){var t=[];for(var n=0;n1?(y=t[t.length-1].length,g+=t.length-1):y+=e.length,b+=e}else e&&(e.start?e.end={row:g,column:y}:e.start={row:g,column:y})}),{text:b,tabstops:a,tokens:u}},m=function(){function e(e){this.index=0,this.ranges=[],this.tabstops=[];if(e.tabstopManager)return e.tabstopManager;e.tabstopManager=this,this.$onChange=this.onChange.bind(this),this.$onChangeSelection=o.delayedCall(this.onChangeSelection.bind(this)).schedule,this.$onChangeSession=this.onChangeSession.bind(this),this.$onAfterExec=this.onAfterExec.bind(this),this.attach(e)}return e.prototype.attach=function(e){this.$openTabstops=null,this.selectedTabstop=null,this.editor=e,this.session=e.session,this.editor.on("change",this.$onChange),this.editor.on("changeSelection",this.$onChangeSelection),this.editor.on("changeSession",this.$onChangeSession),this.editor.commands.on("afterExec",this.$onAfterExec),this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler)},e.prototype.detach=function(){this.tabstops.forEach(this.removeTabstopMarkers,this),this.ranges.length=0,this.tabstops.length=0,this.selectedTabstop=null,this.editor.off("change",this.$onChange),this.editor.off("changeSelection",this.$onChangeSelection),this.editor.off("changeSession",this.$onChangeSession),this.editor.commands.off("afterExec",this.$onAfterExec),this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler),this.editor.tabstopManager=null,this.session=null,this.editor=null},e.prototype.onChange=function(e){var t=e.action[0]=="r",n=this.selectedTabstop||{},r=n.parents||{},i=this.tabstops.slice();for(var s=0;s2&&(this.tabstops.length&&o.push(o.splice(2,1)[0]),this.tabstops.splice.apply(this.tabstops,o))},e.prototype.addTabstopMarkers=function(e){var t=this.session;e.forEach(function(e){e.markerId||(e.markerId=t.addMarker(e,"ace_snippet-marker","text"))})},e.prototype.removeTabstopMarkers=function(e){var t=this.session;e.forEach(function(e){t.removeMarker(e.markerId),e.markerId=null})},e.prototype.removeRange=function(e){var t=e.tabstop.indexOf(e);t!=-1&&e.tabstop.splice(t,1),t=this.ranges.indexOf(e),t!=-1&&this.ranges.splice(t,1),t=e.tabstop.rangeList.ranges.indexOf(e),t!=-1&&e.tabstop.splice(t,1),this.session.removeMarker(e.markerId),e.tabstop.length||(t=this.tabstops.indexOf(e.tabstop),t!=-1&&this.tabstops.splice(t,1),this.tabstops.length||this.detach())},e}();m.prototype.keyboardHandler=new f,m.prototype.keyboardHandler.bindKeys({Tab:function(e){if(t.snippetManager&&t.snippetManager.expandWithTab(e))return;e.tabstopManager.tabNext(1),e.renderer.scrollCursorIntoView()},"Shift-Tab":function(e){e.tabstopManager.tabNext(-1),e.renderer.scrollCursorIntoView()},Esc:function(e){e.tabstopManager.detach()}});var g=function(e,t){e.row==0&&(e.column+=t.column),e.row+=t.row},y=function(e,t){e.row==t.row&&(e.column-=t.column),e.row-=t.row};r.importCssString("\n.ace_snippet-marker {\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n background: rgba(194, 193, 208, 0.09);\n border: 1px dotted rgba(211, 208, 235, 0.62);\n position: absolute;\n}","snippets.css",!1),t.snippetManager=new d;var b=e("./editor").Editor;(function(){this.insertSnippet=function(e,n){return t.snippetManager.insertSnippet(this,e,n)},this.expandSnippet=function(e){return t.snippetManager.expandWithTab(this,e)}}).call(b.prototype)}),define("ace/autocomplete/popup",["require","exports","module","ace/virtual_renderer","ace/editor","ace/range","ace/lib/event","ace/lib/lang","ace/lib/dom","ace/config","ace/lib/useragent"],function(e,t,n){"use strict";var r=e("../virtual_renderer").VirtualRenderer,i=e("../editor").Editor,s=e("../range").Range,o=e("../lib/event"),u=e("../lib/lang"),a=e("../lib/dom"),f=e("../config").nls,l=e("./../lib/useragent"),c=function(e){return"suggest-aria-id:".concat(e)},h=l.isSafari?"menu":"listbox",p=l.isSafari?"menuitem":"option",d=l.isSafari?"aria-current":"aria-selected",v=function(e){var t=new r(e);t.$maxLines=4;var n=new i(t);return n.setHighlightActiveLine(!1),n.setShowPrintMargin(!1),n.renderer.setShowGutter(!1),n.renderer.setHighlightGutterLine(!1),n.$mouseHandler.$focusTimeout=0,n.$highlightTagPending=!0,n},m=function(){function e(e){var t=a.createElement("div"),n=v(t);e&&e.appendChild(t),t.style.display="none",n.renderer.content.style.cursor="default",n.renderer.setStyle("ace_autocomplete"),n.renderer.$textLayer.element.setAttribute("role",h),n.renderer.$textLayer.element.setAttribute("aria-roledescription",f("Autocomplete suggestions")),n.renderer.$textLayer.element.setAttribute("aria-label",f("Autocomplete suggestions")),n.renderer.textarea.setAttribute("aria-hidden","true"),n.setOption("displayIndentGuides",!1),n.setOption("dragDelay",150);var r=function(){};n.focus=r,n.$isFocused=!0,n.renderer.$cursorLayer.restartTimer=r,n.renderer.$cursorLayer.element.style.opacity="0",n.renderer.$maxLines=8,n.renderer.$keepTextAreaAtCursor=!1,n.setHighlightActiveLine(!1),n.session.highlight(""),n.session.$searchHighlight.clazz="ace_highlight-marker",n.on("mousedown",function(e){var t=e.getDocumentPosition();n.selection.moveToPosition(t),m.start.row=m.end.row=t.row,e.stop()});var i,l=new s(-1,0,-1,Infinity),m=new s(-1,0,-1,Infinity);m.id=n.session.addMarker(m,"ace_active-line","fullLine"),n.setSelectOnHover=function(e){e?l.id&&(n.session.removeMarker(l.id),l.id=null):l.id=n.session.addMarker(l,"ace_line-hover","fullLine")},n.setSelectOnHover(!1),n.on("mousemove",function(e){if(!i){i=e;return}if(i.x==e.x&&i.y==e.y)return;i=e,i.scrollTop=n.renderer.scrollTop,n.isMouseOver=!0;var t=i.getDocumentPosition().row;l.start.row!=t&&(l.id||n.setRow(t),y(t))}),n.renderer.on("beforeRender",function(){if(i&&l.start.row!=-1){i.$pos=null;var e=i.getDocumentPosition().row;l.id||n.setRow(e),y(e,!0)}}),n.renderer.on("afterRender",function(){var e=n.getRow(),t=n.renderer.$textLayer,r=t.element.childNodes[e-t.config.firstRow],i=document.activeElement;r!==n.selectedNode&&n.selectedNode&&(a.removeCssClass(n.selectedNode,"ace_selected"),i.removeAttribute("aria-activedescendant"),n.selectedNode.removeAttribute(d),n.selectedNode.removeAttribute("id")),n.selectedNode=r;if(r){a.addCssClass(r,"ace_selected");var s=c(e);r.id=s,t.element.setAttribute("aria-activedescendant",s),i.setAttribute("aria-activedescendant",s),r.setAttribute("role",p),r.setAttribute("aria-roledescription",f("item")),r.setAttribute("aria-label",n.getData(e).value),r.setAttribute("aria-setsize",n.data.length),r.setAttribute("aria-posinset",e+1),r.setAttribute("aria-describedby","doc-tooltip"),r.setAttribute(d,"true")}});var g=function(){y(-1)},y=function(e,t){e!==l.start.row&&(l.start.row=l.end.row=e,t||n.session._emit("changeBackMarker"),n._emit("changeHoverMarker"))};n.getHoveredRow=function(){return l.start.row},o.addListener(n.container,"mouseout",function(){n.isMouseOver=!1,g()}),n.on("hide",g),n.on("changeSelection",g),n.session.doc.getLength=function(){return n.data.length},n.session.doc.getLine=function(e){var t=n.data[e];return typeof t=="string"?t:t&&t.value||""};var b=n.session.bgTokenizer;return b.$tokenizeRow=function(e){function s(e,n){e&&r.push({type:(t.className||"")+(n||""),value:e})}var t=n.data[e],r=[];if(!t)return r;typeof t=="string"&&(t={value:t});var i=t.caption||t.value||t.name,o=i.toLowerCase(),u=(n.filterText||"").toLowerCase(),a=0,f=0;for(var l=0;l<=u.length;l++)if(l!=f&&(t.matchMask&1<=l?r="bottom":r="top"),r==="top"?(c.bottom=e.top-this.$borderSize,c.top=c.bottom-l):r==="bottom"&&(c.top=e.top+t+this.$borderSize,c.bottom=c.top+l);var d=c.top>=0&&c.bottom<=u;if(!s&&!d)return!1;d?f.$maxPixelHeight=null:r==="top"?f.$maxPixelHeight=p:f.$maxPixelHeight=h,r==="top"?(o.style.top="",o.style.bottom=u-c.bottom+"px",n.isTopdown=!1):(o.style.top=c.top+"px",o.style.bottom="",n.isTopdown=!0),o.style.display="";var v=e.left;return v+o.offsetWidth>a&&(v=a-o.offsetWidth),o.style.left=v+"px",o.style.right="",n.isOpen||(n.isOpen=!0,this._signal("show"),i=null),n.anchorPos=e,n.anchor=r,!0},n.show=function(e,t,n){this.tryShow(e,t,n?"bottom":undefined,!0)},n.goTo=function(e){var t=this.getRow(),n=this.session.getLength()-1;switch(e){case"up":t=t<=0?n:t-1;break;case"down":t=t>=n?-1:t+1;break;case"start":t=0;break;case"end":t=n}this.setRow(t)},n.getTextLeftOffset=function(){return this.$borderSize+this.renderer.$padding+this.$imageSize},n.$imageSize=0,n.$borderSize=1,n}return e}();a.importCssString('\n.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\n background-color: #CAD6FA;\n z-index: 1;\n}\n.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\n background-color: #3a674e;\n}\n.ace_editor.ace_autocomplete .ace_line-hover {\n border: 1px solid #abbffe;\n margin-top: -1px;\n background: rgba(233,233,253,0.4);\n position: absolute;\n z-index: 2;\n}\n.ace_dark.ace_editor.ace_autocomplete .ace_line-hover {\n border: 1px solid rgba(109, 150, 13, 0.8);\n background: rgba(58, 103, 78, 0.62);\n}\n.ace_completion-meta {\n opacity: 0.5;\n margin-left: 0.9em;\n}\n.ace_completion-message {\n margin-left: 0.9em;\n color: blue;\n}\n.ace_editor.ace_autocomplete .ace_completion-highlight{\n color: #2d69c7;\n}\n.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight{\n color: #93ca12;\n}\n.ace_editor.ace_autocomplete {\n width: 300px;\n z-index: 200000;\n border: 1px lightgray solid;\n position: fixed;\n box-shadow: 2px 3px 5px rgba(0,0,0,.2);\n line-height: 1.4;\n background: #fefefe;\n color: #111;\n}\n.ace_dark.ace_editor.ace_autocomplete {\n border: 1px #484747 solid;\n box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);\n line-height: 1.4;\n background: #25282c;\n color: #c1c1c1;\n}\n.ace_autocomplete .ace_text-layer {\n width: calc(100% - 8px);\n}\n.ace_autocomplete .ace_line {\n display: flex;\n align-items: center;\n}\n.ace_autocomplete .ace_line > * {\n min-width: 0;\n flex: 0 0 auto;\n}\n.ace_autocomplete .ace_line .ace_ {\n flex: 0 1 auto;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.ace_autocomplete .ace_completion-spacer {\n flex: 1;\n}\n.ace_autocomplete.ace_loading:after {\n content: "";\n position: absolute;\n top: 0px;\n height: 2px;\n width: 8%;\n background: blue;\n z-index: 100;\n animation: ace_progress 3s infinite linear;\n animation-delay: 300ms;\n transform: translateX(-100%) scaleX(1);\n}\n@keyframes ace_progress {\n 0% { transform: translateX(-100%) scaleX(1) }\n 50% { transform: translateX(625%) scaleX(2) } \n 100% { transform: translateX(1500%) scaleX(3) } \n}\n@media (prefers-reduced-motion) {\n .ace_autocomplete.ace_loading:after {\n transform: translateX(625%) scaleX(2);\n animation: none;\n }\n}\n',"autocompletion.css",!1),t.AcePopup=m,t.$singleLineEditor=v,t.getAriaId=c}),define("ace/autocomplete/inline_screenreader",["require","exports","module"],function(e,t,n){"use strict";var r=function(){function e(e){this.editor=e,this.screenReaderDiv=document.createElement("div"),this.screenReaderDiv.classList.add("ace_screenreader-only"),this.editor.container.appendChild(this.screenReaderDiv)}return e.prototype.setScreenReaderContent=function(e){!this.popup&&this.editor.completer&&this.editor.completer.popup&&(this.popup=this.editor.completer.popup,this.popup.renderer.on("afterRender",function(){var e=this.popup.getRow(),t=this.popup.renderer.$textLayer,n=t.element.childNodes[e-t.config.firstRow];if(n){var r="doc-tooltip ";for(var i=0;i=0;s--){if(!n.test(e[s]))break;i.push(e[s])}return i.reverse().join("")},t.retrieveFollowingIdentifier=function(e,t,n){n=n||r;var i=[];for(var s=t;s0)for(var t=this.popup.getFirstVisibleRow();t<=this.popup.getLastVisibleRow();t++){var n=this.popup.getData(t);n&&(!e||n.hideInlinePreview)&&this.$seen(n)}},e.prototype.$onPopupShow=function(e){this.$onPopupChange(e),this.stickySelection=!1,this.stickySelectionDelay>=0&&this.stickySelectionTimer.schedule(this.stickySelectionDelay)},e.prototype.observeLayoutChanges=function(){if(this.$elements||!this.editor)return;window.addEventListener("resize",this.onLayoutChange,{passive:!0}),window.addEventListener("wheel",this.mousewheelListener);var e=this.editor.container.parentNode,t=[];while(e)t.push(e),e.addEventListener("scroll",this.onLayoutChange,{passive:!0}),e=e.parentNode;this.$elements=t},e.prototype.unObserveLayoutChanges=function(){var e=this;window.removeEventListener("resize",this.onLayoutChange,{passive:!0}),window.removeEventListener("wheel",this.mousewheelListener),this.$elements&&this.$elements.forEach(function(t){t.removeEventListener("scroll",e.onLayoutChange,{passive:!0})}),this.$elements=null},e.prototype.onLayoutChange=function(){if(!this.popup.isOpen)return this.unObserveLayoutChanges();this.$updatePopupPosition(),this.updateDocTooltip()},e.prototype.$updatePopupPosition=function(){var e=this.editor,t=e.renderer,n=t.layerConfig.lineHeight,r=t.$cursorLayer.getPixelPosition(this.base,!0);r.left-=this.popup.getTextLeftOffset();var i=e.container.getBoundingClientRect();r.top+=i.top-t.layerConfig.offset,r.left+=i.left-e.renderer.scrollLeft,r.left+=t.gutterWidth;var s={top:r.top,left:r.left};t.$ghostText&&t.$ghostTextWidget&&this.base.row===t.$ghostText.position.row&&(s.top+=t.$ghostTextWidget.el.offsetHeight);var o=e.container.getBoundingClientRect().bottom-n,u=othis.filterText&&e.lastIndexOf(this.filterText,0)===0)var t=this.filtered;else var t=this.all;this.filterText=e,t=this.filterCompletions(t,this.filterText),t=t.sort(function(e,t){return t.exactMatch-e.exactMatch||t.$score-e.$score||(e.caption||e.value).localeCompare(t.caption||t.value)});var n=null;t=t.filter(function(e){var t=e.snippet||e.caption||e.value;return t===n?!1:(n=t,!0)}),this.filtered=t},e.prototype.filterCompletions=function(e,t){var n=[],r=t.toUpperCase(),i=t.toLowerCase();e:for(var s=0,o;o=e[s];s++){var u=!this.ignoreCaption&&o.caption||o.value||o.snippet;if(!u)continue;var a=-1,f=0,l=0,c,h;if(this.exactMatch){if(t!==u.substr(0,t.length))continue e}else{var p=u.toLowerCase().indexOf(i);if(p>-1)l=p;else for(var d=0;d=0?m<0||v0&&(a===-1&&(l+=10),l+=h,f|=1<",o.escapeHTML(e.caption),"","
",o.escapeHTML(l(e.snippet))].join(""))},id:"snippetCompleter"},h=[c,a,f];t.setCompleters=function(e){h.length=0,e&&h.push.apply(h,e)},t.addCompleter=function(e){h.push(e)},t.textCompleter=a,t.keyWordCompleter=f,t.snippetCompleter=c;var p={name:"expandSnippet",exec:function(e){return r.expandWithTab(e)},bindKey:"Tab"},d=function(e,t){v(t.session.$mode)},v=function(e){typeof e=="string"&&(e=s.$modes[e]);if(!e)return;r.files||(r.files={}),m(e.$id,e.snippetFileId),e.modes&&e.modes.forEach(v)},m=function(e,t){if(!t||!e||r.files[e])return;r.files[e]={},s.loadModule(t,function(t){if(!t)return;r.files[e]=t,!t.snippets&&t.snippetText&&(t.snippets=r.parseSnippetFile(t.snippetText)),r.register(t.snippets||[],t.scope),t.includeScopes&&(r.snippetMap[t.scope].includeScopes=t.includeScopes,t.includeScopes.forEach(function(e){v("ace/mode/"+e)}))})},g=function(e){var t=e.editor,n=t.completer&&t.completer.activated;if(e.command.name==="backspace")n&&!u.getCompletionPrefix(t)&&t.completer.detach();else if(e.command.name==="insertstring"&&!n){y=e;var r=e.editor.$liveAutocompletionDelay;r?b.delay(r):w(e)}},y,b=o.delayedCall(function(){w(y)},0),w=function(e){var t=e.editor,n=u.getCompletionPrefix(t),r=u.triggerAutocomplete(t);if(n&&n.length>=t.$liveAutocompletionThreshold||r){var s=i.for(t);s.autoShown=!0,s.showPopup(t)}},E=e("../editor").Editor;e("../config").defineOptions(E.prototype,"editor",{enableBasicAutocompletion:{set:function(e){e?(this.completers||(this.completers=Array.isArray(e)?e:h),this.commands.addCommand(i.startCommand)):this.commands.removeCommand(i.startCommand)},value:!1},enableLiveAutocompletion:{set:function(e){e?(this.completers||(this.completers=Array.isArray(e)?e:h),this.commands.on("afterExec",g)):this.commands.off("afterExec",g)},value:!1},liveAutocompletionDelay:{initialValue:0},liveAutocompletionThreshold:{initialValue:0},enableSnippets:{set:function(e){e?(this.commands.addCommand(p),this.on("changeMode",d),d(null,this)):(this.commands.removeCommand(p),this.off("changeMode",d))},value:!1}})}); (function() { + window.require(["ace/ext/language_tools"], function(m) { + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = m; + } + }); + })(); + \ No newline at end of file diff --git a/static/js/ace/mode-markdown.js b/static/js/ace/mode-markdown.js new file mode 100644 index 0000000..402843a --- /dev/null +++ b/static/js/ace/mode-markdown.js @@ -0,0 +1,8 @@ +define("ace/mode/css_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text_highlight_rules").TextHighlightRules,o=t.supportType="align-content|align-items|align-self|all|animation|animation-delay|animation-direction|animation-duration|animation-fill-mode|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|backface-visibility|background|background-attachment|background-blend-mode|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|border|border-bottom|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-collapse|border-color|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|border-right-color|border-right-style|border-right-width|border-spacing|border-style|border-top|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-width|bottom|box-shadow|box-sizing|caption-side|clear|clip|color|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|content|counter-increment|counter-reset|cursor|direction|display|empty-cells|filter|flex|flex-basis|flex-direction|flex-flow|flex-grow|flex-shrink|flex-wrap|float|font|font-family|font-size|font-size-adjust|font-stretch|font-style|font-variant|font-weight|hanging-punctuation|height|justify-content|left|letter-spacing|line-height|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|max-height|max-width|max-zoom|min-height|min-width|min-zoom|nav-down|nav-index|nav-left|nav-right|nav-up|opacity|order|outline|outline-color|outline-offset|outline-style|outline-width|overflow|overflow-x|overflow-y|padding|padding-bottom|padding-left|padding-right|padding-top|page-break-after|page-break-before|page-break-inside|perspective|perspective-origin|position|quotes|resize|right|tab-size|table-layout|text-align|text-align-last|text-decoration|text-decoration-color|text-decoration-line|text-decoration-style|text-indent|text-justify|text-overflow|text-shadow|text-transform|top|transform|transform-origin|transform-style|transition|transition-delay|transition-duration|transition-property|transition-timing-function|unicode-bidi|user-select|user-zoom|vertical-align|visibility|white-space|width|word-break|word-spacing|word-wrap|z-index",u=t.supportFunction="rgb|rgba|url|attr|counter|counters",a=t.supportConstant="absolute|after-edge|after|all-scroll|all|alphabetic|always|antialiased|armenian|auto|avoid-column|avoid-page|avoid|balance|baseline|before-edge|before|below|bidi-override|block-line-height|block|bold|bolder|border-box|both|bottom|box|break-all|break-word|capitalize|caps-height|caption|center|central|char|circle|cjk-ideographic|clone|close-quote|col-resize|collapse|column|consider-shifts|contain|content-box|cover|crosshair|cubic-bezier|dashed|decimal-leading-zero|decimal|default|disabled|disc|disregard-shifts|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ease-in|ease-in-out|ease-out|ease|ellipsis|end|exclude-ruby|flex-end|flex-start|fill|fixed|georgian|glyphs|grid-height|groove|hand|hanging|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|icon|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|ideographic|inactive|include-ruby|inherit|initial|inline-block|inline-box|inline-line-height|inline-table|inline|inset|inside|inter-ideograph|inter-word|invert|italic|justify|katakana-iroha|katakana|keep-all|last|left|lighter|line-edge|line-through|line|linear|list-item|local|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|mathematical|max-height|max-size|medium|menu|message-box|middle|move|n-resize|ne-resize|newspaper|no-change|no-close-quote|no-drop|no-open-quote|no-repeat|none|normal|not-allowed|nowrap|nw-resize|oblique|open-quote|outset|outside|overline|padding-box|page|pointer|pre-line|pre-wrap|pre|preserve-3d|progress|relative|repeat-x|repeat-y|repeat|replaced|reset-size|ridge|right|round|row-resize|rtl|s-resize|scroll|se-resize|separate|slice|small-caps|small-caption|solid|space|square|start|static|status-bar|step-end|step-start|steps|stretch|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table-row|table|tb-rl|text-after-edge|text-before-edge|text-bottom|text-size|text-top|text|thick|thin|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|use-script|vertical-ideographic|vertical-text|visible|w-resize|wait|whitespace|z-index|zero|zoom",f=t.supportConstantColor="aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|rebeccapurple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen",l=t.supportConstantFonts="arial|century|comic|courier|cursive|fantasy|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace",c=t.numRe="\\-?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+))",h=t.pseudoElements="(\\:+)\\b(after|before|first-letter|first-line|moz-selection|selection)\\b",p=t.pseudoClasses="(:)\\b(active|checked|disabled|empty|enabled|first-child|first-of-type|focus|hover|indeterminate|invalid|last-child|last-of-type|link|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|only-child|only-of-type|required|root|target|valid|visited)\\b",d=function(){var e=this.createKeywordMapper({"support.function":u,"support.constant":a,"support.type":o,"support.constant.color":f,"support.constant.fonts":l},"text",!0);this.$rules={start:[{include:["strings","url","comments"]},{token:"paren.lparen",regex:"\\{",next:"ruleset"},{token:"paren.rparen",regex:"\\}"},{token:"string",regex:"@(?!viewport)",next:"media"},{token:"keyword",regex:"#[a-z0-9-_]+"},{token:"keyword",regex:"%"},{token:"variable",regex:"\\.[a-z0-9-_]+"},{token:"string",regex:":[a-z0-9-_]+"},{token:"constant.numeric",regex:c},{token:"constant",regex:"[a-z0-9-_]+"},{caseInsensitive:!0}],media:[{include:["strings","url","comments"]},{token:"paren.lparen",regex:"\\{",next:"start"},{token:"paren.rparen",regex:"\\}",next:"start"},{token:"string",regex:";",next:"start"},{token:"keyword",regex:"(?:media|supports|document|charset|import|namespace|media|supports|document|page|font|keyframes|viewport|counter-style|font-feature-values|swash|ornaments|annotation|stylistic|styleset|character-variant)"}],comments:[{token:"comment",regex:"\\/\\*",push:[{token:"comment",regex:"\\*\\/",next:"pop"},{defaultToken:"comment"}]}],ruleset:[{regex:"-(webkit|ms|moz|o)-",token:"text"},{token:"punctuation.operator",regex:"[:;]"},{token:"paren.rparen",regex:"\\}",next:"start"},{include:["strings","url","comments"]},{token:["constant.numeric","keyword"],regex:"("+c+")(ch|cm|deg|em|ex|fr|gd|grad|Hz|in|kHz|mm|ms|pc|pt|px|rad|rem|s|turn|vh|vmax|vmin|vm|vw|%)"},{token:"constant.numeric",regex:c},{token:"constant.numeric",regex:"#[a-f0-9]{6}"},{token:"constant.numeric",regex:"#[a-f0-9]{3}"},{token:["punctuation","entity.other.attribute-name.pseudo-element.css"],regex:h},{token:["punctuation","entity.other.attribute-name.pseudo-class.css"],regex:p},{include:"url"},{token:e,regex:"\\-?[a-zA-Z_][a-zA-Z0-9_\\-]*"},{caseInsensitive:!0}],url:[{token:"support.function",regex:"(?:url(:?-prefix)?|domain|regexp)\\(",push:[{token:"support.function",regex:"\\)",next:"pop"},{defaultToken:"string"}]}],strings:[{token:"string.start",regex:"'",push:[{token:"string.end",regex:"'|$",next:"pop"},{include:"escapes"},{token:"constant.language.escape",regex:/\\$/,consumeLineEnd:!0},{defaultToken:"string"}]},{token:"string.start",regex:'"',push:[{token:"string.end",regex:'"|$',next:"pop"},{include:"escapes"},{token:"constant.language.escape",regex:/\\$/,consumeLineEnd:!0},{defaultToken:"string"}]}],escapes:[{token:"constant.language.escape",regex:/\\([a-fA-F\d]{1,6}|[^a-fA-F\d])/}]},this.normalizeRules()};r.inherits(d,s),t.CssHighlightRules=d}),define("ace/mode/jsdoc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:["comment.doc.tag","comment.doc.text","lparen.doc"],regex:"(@(?:param|member|typedef|property|namespace|var|const|callback))(\\s*)({)",push:[{token:"lparen.doc",regex:"{",push:[{include:"doc-syntax"},{token:"rparen.doc",regex:"}|(?=$)",next:"pop"}]},{token:["rparen.doc","text.doc","variable.parameter.doc","lparen.doc","variable.parameter.doc","rparen.doc"],regex:/(})(\s*)(?:([\w=:\/\.]+)|(?:(\[)([\w=:\/\.]+)(\])))/,next:"pop"},{token:"rparen.doc",regex:"}|(?=$)",next:"pop"},{include:"doc-syntax"},{defaultToken:"text.doc"}]},{token:["comment.doc.tag","text.doc","lparen.doc"],regex:"(@(?:returns?|yields|type|this|suppress|public|protected|private|package|modifies|implements|external|exception|throws|enum|define|extends))(\\s*)({)",push:[{token:"lparen.doc",regex:"{",push:[{include:"doc-syntax"},{token:"rparen.doc",regex:"}|(?=$)",next:"pop"}]},{token:"rparen.doc",regex:"}|(?=$)",next:"pop"},{include:"doc-syntax"},{defaultToken:"text.doc"}]},{token:["comment.doc.tag","text.doc","variable.parameter.doc"],regex:'(@(?:alias|memberof|instance|module|name|lends|namespace|external|this|template|requires|param|implements|function|extends|typedef|mixes|constructor|var|memberof\\!|event|listens|exports|class|constructs|interface|emits|fires|throws|const|callback|borrows|augments))(\\s+)(\\w[\\w#.:/~"\\-]*)?'},{token:["comment.doc.tag","text.doc","variable.parameter.doc"],regex:"(@method)(\\s+)(\\w[\\w.\\(\\)]*)"},{token:"comment.doc.tag",regex:"@access\\s+(?:private|public|protected)"},{token:"comment.doc.tag",regex:"@kind\\s+(?:class|constant|event|external|file|function|member|mixin|module|namespace|typedef)"},{token:"comment.doc.tag",regex:"@\\w+(?=\\s|$)"},s.getTagRule(),{defaultToken:"comment.doc",caseInsensitive:!0}],"doc-syntax":[{token:"operator.doc",regex:/[|:]/},{token:"paren.doc",regex:/[\[\]]/}]},this.normalizeRules()};r.inherits(s,i),s.getTagRule=function(e){return{token:"comment.doc.tag.storage.type",regex:"\\b(?:TODO|FIXME|XXX|HACK)\\b"}},s.getStartRule=function(e){return{token:"comment.doc",regex:"\\/\\*(?=\\*)",next:e}},s.getEndRule=function(e){return{token:"comment.doc",regex:"\\*\\/",next:e}},t.JsDocCommentHighlightRules=s}),define("ace/mode/javascript_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/jsdoc_comment_highlight_rules","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){var r=e.charAt(1)=="/"?2:1;if(r==1)t!=this.nextState?n.unshift(this.next,this.nextState,0):n.unshift(this.next),n[2]++;else if(r==2&&t==this.nextState){n[1]--;if(!n[1]||n[1]<0)n.shift(),n.shift()}return[{type:"meta.tag.punctuation."+(r==1?"":"end-")+"tag-open.xml",value:e.slice(0,r)},{type:"meta.tag.tag-name.xml",value:e.substr(r)}]},regex:"",onMatch:function(e,t,n){return t==n[0]&&n.shift(),e.length==2&&(n[0]==this.nextState&&n[1]--,(!n[1]||n[1]<0)&&n.splice(0,2)),this.next=n[0]||"start",[{type:this.token,value:e}]},nextState:"jsx"},n,f("jsxAttributes"),{token:"entity.other.attribute-name.xml",regex:e},{token:"keyword.operator.attribute-equals.xml",regex:"="},{token:"text.tag-whitespace.xml",regex:"\\s+"},{token:"string.attribute-value.xml",regex:"'",stateName:"jsx_attr_q",push:[{token:"string.attribute-value.xml",regex:"'",next:"pop"},{include:"reference"},{defaultToken:"string.attribute-value.xml"}]},{token:"string.attribute-value.xml",regex:'"',stateName:"jsx_attr_qq",push:[{token:"string.attribute-value.xml",regex:'"',next:"pop"},{include:"reference"},{defaultToken:"string.attribute-value.xml"}]},t],this.$rules.reference=[{token:"constant.language.escape.reference.xml",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"}]}function f(e){return[{token:"comment",regex:/\/\*/,next:[i.getTagRule(),{token:"comment",regex:"\\*\\/",next:e||"pop"},{defaultToken:"comment",caseInsensitive:!0}]},{token:"comment",regex:"\\/\\/",next:[i.getTagRule(),{token:"comment",regex:"$|^",next:e||"pop"},{defaultToken:"comment",caseInsensitive:!0}]}]}var r=e("../lib/oop"),i=e("./jsdoc_comment_highlight_rules").JsDocCommentHighlightRules,s=e("./text_highlight_rules").TextHighlightRules,o="[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*",u=function(e){var t=this.createKeywordMapper({"variable.language":"Array|Boolean|Date|Function|Iterator|Number|Object|RegExp|String|Proxy|Symbol|Namespace|QName|XML|XMLList|ArrayBuffer|Float32Array|Float64Array|Int16Array|Int32Array|Int8Array|Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|Error|EvalError|InternalError|RangeError|ReferenceError|StopIteration|SyntaxError|TypeError|URIError|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt|JSON|Math|this|arguments|prototype|window|document",keyword:"const|yield|import|get|set|async|await|break|case|catch|continue|default|delete|do|else|finally|for|function|if|in|of|instanceof|new|return|switch|throw|try|typeof|let|var|while|with|debugger|__parent__|__count__|escape|unescape|with|__proto__|class|enum|extends|super|export|implements|private|public|interface|package|protected|static|constructor","storage.type":"const|let|var|function","constant.language":"null|Infinity|NaN|undefined","support.function":"alert","constant.language.boolean":"true|false"},"identifier"),n="case|do|else|finally|in|instanceof|return|throw|try|typeof|yield|void",r="\\\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|u{[0-9a-fA-F]{1,6}}|[0-2][0-7]{0,2}|3[0-7][0-7]?|[4-7][0-7]?|.)";this.$rules={no_regex:[i.getStartRule("doc-start"),f("no_regex"),{token:"string",regex:"'(?=.)",next:"qstring"},{token:"string",regex:'"(?=.)',next:"qqstring"},{token:"constant.numeric",regex:/0(?:[xX][0-9a-fA-F]+|[oO][0-7]+|[bB][01]+)\b/},{token:"constant.numeric",regex:/(?:\d\d*(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+\b)?/},{token:["storage.type","punctuation.operator","support.function","punctuation.operator","entity.name.function","text","keyword.operator"],regex:"("+o+")(\\.)(prototype)(\\.)("+o+")(\\s*)(=)",next:"function_arguments"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function\\*?)(\\s*)(\\()",next:"function_arguments"},{token:["entity.name.function","text","keyword.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\s*)(=)(\\s*)(function\\*?)(\\s*)(\\()",next:"function_arguments"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","entity.name.function","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function\\*?)(\\s+)(\\w+)(\\s*)(\\()",next:"function_arguments"},{token:["storage.type","text","entity.name.function","text","paren.lparen"],regex:"(function\\*?)(\\s+)("+o+")(\\s*)(\\()",next:"function_arguments"},{token:["entity.name.function","text","punctuation.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\s*)(:)(\\s*)(function\\*?)(\\s*)(\\()",next:"function_arguments"},{token:["text","text","storage.type","text","paren.lparen"],regex:"(:)(\\s*)(function\\*?)(\\s*)(\\()",next:"function_arguments"},{token:"keyword",regex:"from(?=\\s*('|\"))"},{token:"keyword",regex:"(?:"+n+")\\b",next:"start"},{token:"support.constant",regex:/that\b/},{token:["storage.type","punctuation.operator","support.function.firebug"],regex:/(console)(\.)(warn|info|log|error|debug|time|trace|timeEnd|assert)\b/},{token:t,regex:o},{token:"punctuation.operator",regex:/[.](?![.])/,next:"property"},{token:"storage.type",regex:/=>/,next:"start"},{token:"keyword.operator",regex:/--|\+\+|\.{3}|===|==|=|!=|!==|<+=?|>+=?|!|&&|\|\||\?:|[!$%&*+\-~\/^]=?/,next:"start"},{token:"punctuation.operator",regex:/[?:,;.]/,next:"start"},{token:"paren.lparen",regex:/[\[({]/,next:"start"},{token:"paren.rparen",regex:/[\])}]/},{token:"comment",regex:/^#!.*$/}],property:[{token:"text",regex:"\\s+"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","entity.name.function","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function\\*?)(?:(\\s+)(\\w+))?(\\s*)(\\()",next:"function_arguments"},{token:"punctuation.operator",regex:/[.](?![.])/},{token:"support.function",regex:/(s(?:h(?:ift|ow(?:Mod(?:elessDialog|alDialog)|Help))|croll(?:X|By(?:Pages|Lines)?|Y|To)?|t(?:op|rike)|i(?:n|zeToContent|debar|gnText)|ort|u(?:p|b(?:str(?:ing)?)?)|pli(?:ce|t)|e(?:nd|t(?:Re(?:sizable|questHeader)|M(?:i(?:nutes|lliseconds)|onth)|Seconds|Ho(?:tKeys|urs)|Year|Cursor|Time(?:out)?|Interval|ZOptions|Date|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Date|FullYear)|FullYear|Active)|arch)|qrt|lice|avePreferences|mall)|h(?:ome|andleEvent)|navigate|c(?:har(?:CodeAt|At)|o(?:s|n(?:cat|textual|firm)|mpile)|eil|lear(?:Timeout|Interval)?|a(?:ptureEvents|ll)|reate(?:StyleSheet|Popup|EventObject))|t(?:o(?:GMTString|S(?:tring|ource)|U(?:TCString|pperCase)|Lo(?:caleString|werCase))|est|a(?:n|int(?:Enabled)?))|i(?:s(?:NaN|Finite)|ndexOf|talics)|d(?:isableExternalCapture|ump|etachEvent)|u(?:n(?:shift|taint|escape|watch)|pdateCommands)|j(?:oin|avaEnabled)|p(?:o(?:p|w)|ush|lugins.refresh|a(?:ddings|rse(?:Int|Float)?)|r(?:int|ompt|eference))|e(?:scape|nableExternalCapture|val|lementFromPoint|x(?:p|ec(?:Script|Command)?))|valueOf|UTC|queryCommand(?:State|Indeterm|Enabled|Value)|f(?:i(?:nd|lter|le(?:ModifiedDate|Size|CreatedDate|UpdatedDate)|xed)|o(?:nt(?:size|color)|rward|rEach)|loor|romCharCode)|watch|l(?:ink|o(?:ad|g)|astIndexOf)|a(?:sin|nchor|cos|t(?:tachEvent|ob|an(?:2)?)|pply|lert|b(?:s|ort))|r(?:ou(?:nd|teEvents)|e(?:size(?:By|To)|calc|turnValue|place|verse|l(?:oad|ease(?:Capture|Events)))|andom)|g(?:o|et(?:ResponseHeader|M(?:i(?:nutes|lliseconds)|onth)|Se(?:conds|lection)|Hours|Year|Time(?:zoneOffset)?|Da(?:y|te)|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Da(?:y|te)|FullYear)|FullYear|A(?:ttention|llResponseHeaders)))|m(?:in|ove(?:B(?:y|elow)|To(?:Absolute)?|Above)|ergeAttributes|a(?:tch|rgins|x))|b(?:toa|ig|o(?:ld|rderWidths)|link|ack))\b(?=\()/},{token:"support.function.dom",regex:/(s(?:ub(?:stringData|mit)|plitText|e(?:t(?:NamedItem|Attribute(?:Node)?)|lect))|has(?:ChildNodes|Feature)|namedItem|c(?:l(?:ick|o(?:se|neNode))|reate(?:C(?:omment|DATASection|aption)|T(?:Head|extNode|Foot)|DocumentFragment|ProcessingInstruction|E(?:ntityReference|lement)|Attribute))|tabIndex|i(?:nsert(?:Row|Before|Cell|Data)|tem)|open|delete(?:Row|C(?:ell|aption)|T(?:Head|Foot)|Data)|focus|write(?:ln)?|a(?:dd|ppend(?:Child|Data))|re(?:set|place(?:Child|Data)|move(?:NamedItem|Child|Attribute(?:Node)?)?)|get(?:NamedItem|Element(?:sBy(?:Name|TagName|ClassName)|ById)|Attribute(?:Node)?)|blur)\b(?=\()/},{token:"support.constant",regex:/(s(?:ystemLanguage|cr(?:ipts|ollbars|een(?:X|Y|Top|Left))|t(?:yle(?:Sheets)?|atus(?:Text|bar)?)|ibling(?:Below|Above)|ource|uffixes|e(?:curity(?:Policy)?|l(?:ection|f)))|h(?:istory|ost(?:name)?|as(?:h|Focus))|y|X(?:MLDocument|SLDocument)|n(?:ext|ame(?:space(?:s|URI)|Prop))|M(?:IN_VALUE|AX_VALUE)|c(?:haracterSet|o(?:n(?:structor|trollers)|okieEnabled|lorDepth|mp(?:onents|lete))|urrent|puClass|l(?:i(?:p(?:boardData)?|entInformation)|osed|asses)|alle(?:e|r)|rypto)|t(?:o(?:olbar|p)|ext(?:Transform|Indent|Decoration|Align)|ags)|SQRT(?:1_2|2)|i(?:n(?:ner(?:Height|Width)|put)|ds|gnoreCase)|zIndex|o(?:scpu|n(?:readystatechange|Line)|uter(?:Height|Width)|p(?:sProfile|ener)|ffscreenBuffering)|NEGATIVE_INFINITY|d(?:i(?:splay|alog(?:Height|Top|Width|Left|Arguments)|rectories)|e(?:scription|fault(?:Status|Ch(?:ecked|arset)|View)))|u(?:ser(?:Profile|Language|Agent)|n(?:iqueID|defined)|pdateInterval)|_content|p(?:ixelDepth|ort|ersonalbar|kcs11|l(?:ugins|atform)|a(?:thname|dding(?:Right|Bottom|Top|Left)|rent(?:Window|Layer)?|ge(?:X(?:Offset)?|Y(?:Offset)?))|r(?:o(?:to(?:col|type)|duct(?:Sub)?|mpter)|e(?:vious|fix)))|e(?:n(?:coding|abledPlugin)|x(?:ternal|pando)|mbeds)|v(?:isibility|endor(?:Sub)?|Linkcolor)|URLUnencoded|P(?:I|OSITIVE_INFINITY)|f(?:ilename|o(?:nt(?:Size|Family|Weight)|rmName)|rame(?:s|Element)|gColor)|E|whiteSpace|l(?:i(?:stStyleType|n(?:eHeight|kColor))|o(?:ca(?:tion(?:bar)?|lName)|wsrc)|e(?:ngth|ft(?:Context)?)|a(?:st(?:M(?:odified|atch)|Index|Paren)|yer(?:s|X)|nguage))|a(?:pp(?:MinorVersion|Name|Co(?:deName|re)|Version)|vail(?:Height|Top|Width|Left)|ll|r(?:ity|guments)|Linkcolor|bove)|r(?:ight(?:Context)?|e(?:sponse(?:XML|Text)|adyState))|global|x|m(?:imeTypes|ultiline|enubar|argin(?:Right|Bottom|Top|Left))|L(?:N(?:10|2)|OG(?:10E|2E))|b(?:o(?:ttom|rder(?:Width|RightWidth|BottomWidth|Style|Color|TopWidth|LeftWidth))|ufferDepth|elow|ackground(?:Color|Image)))\b/},{token:"identifier",regex:o},{regex:"",token:"empty",next:"no_regex"}],start:[i.getStartRule("doc-start"),f("start"),{token:"string.regexp",regex:"\\/",next:"regex"},{token:"text",regex:"\\s+|^$",next:"start"},{token:"empty",regex:"",next:"no_regex"}],regex:[{token:"regexp.keyword.operator",regex:"\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"},{token:"string.regexp",regex:"/[sxngimy]*",next:"no_regex"},{token:"invalid",regex:/\{\d+\b,?\d*\}[+*]|[+*$^?][+*]|[$^][?]|\?{3,}/},{token:"constant.language.escape",regex:/\(\?[:=!]|\)|\{\d+\b,?\d*\}|[+*]\?|[()$^+*?.]/},{token:"constant.language.delimiter",regex:/\|/},{token:"constant.language.escape",regex:/\[\^?/,next:"regex_character_class"},{token:"empty",regex:"$",next:"no_regex"},{defaultToken:"string.regexp"}],regex_character_class:[{token:"regexp.charclass.keyword.operator",regex:"\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"},{token:"constant.language.escape",regex:"]",next:"regex"},{token:"constant.language.escape",regex:"-"},{token:"empty",regex:"$",next:"no_regex"},{defaultToken:"string.regexp.charachterclass"}],default_parameter:[{token:"string",regex:"'(?=.)",push:[{token:"string",regex:"'|$",next:"pop"},{include:"qstring"}]},{token:"string",regex:'"(?=.)',push:[{token:"string",regex:'"|$',next:"pop"},{include:"qqstring"}]},{token:"constant.language",regex:"null|Infinity|NaN|undefined"},{token:"constant.numeric",regex:/0(?:[xX][0-9a-fA-F]+|[oO][0-7]+|[bB][01]+)\b/},{token:"constant.numeric",regex:/(?:\d\d*(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+\b)?/},{token:"punctuation.operator",regex:",",next:"function_arguments"},{token:"text",regex:"\\s+"},{token:"punctuation.operator",regex:"$"},{token:"empty",regex:"",next:"no_regex"}],function_arguments:[f("function_arguments"),{token:"variable.parameter",regex:o},{token:"punctuation.operator",regex:","},{token:"text",regex:"\\s+"},{token:"punctuation.operator",regex:"$"},{token:"empty",regex:"",next:"no_regex"}],qqstring:[{token:"constant.language.escape",regex:r},{token:"string",regex:"\\\\$",consumeLineEnd:!0},{token:"string",regex:'"|$',next:"no_regex"},{defaultToken:"string"}],qstring:[{token:"constant.language.escape",regex:r},{token:"string",regex:"\\\\$",consumeLineEnd:!0},{token:"string",regex:"'|$",next:"no_regex"},{defaultToken:"string"}]};if(!e||!e.noES6)this.$rules.no_regex.unshift({regex:"[{}]",onMatch:function(e,t,n){this.next=e=="{"?this.nextState:"";if(e=="{"&&n.length)n.unshift("start",t);else if(e=="}"&&n.length){n.shift(),this.next=n.shift();if(this.next.indexOf("string")!=-1||this.next.indexOf("jsx")!=-1)return"paren.quasi.end"}return e=="{"?"paren.lparen":"paren.rparen"},nextState:"start"},{token:"string.quasi.start",regex:/`/,push:[{token:"constant.language.escape",regex:r},{token:"paren.quasi.start",regex:/\${/,push:"start"},{token:"string.quasi.end",regex:/`/,next:"pop"},{defaultToken:"string.quasi"}]},{token:["variable.parameter","text"],regex:"("+o+")(\\s*)(?=\\=>)"},{token:"paren.lparen",regex:"(\\()(?=.+\\s*=>)",next:"function_arguments"},{token:"variable.language",regex:"(?:(?:(?:Weak)?(?:Set|Map))|Promise)\\b"}),this.$rules.function_arguments.unshift({token:"keyword.operator",regex:"=",next:"default_parameter"},{token:"keyword.operator",regex:"\\.{3}"}),this.$rules.property.unshift({token:"support.function",regex:"(findIndex|repeat|startsWith|endsWith|includes|isSafeInteger|trunc|cbrt|log2|log10|sign|then|catch|finally|resolve|reject|race|any|all|allSettled|keys|entries|isInteger)\\b(?=\\()"},{token:"constant.language",regex:"(?:MAX_SAFE_INTEGER|MIN_SAFE_INTEGER|EPSILON)\\b"}),(!e||e.jsx!=0)&&a.call(this);this.embedRules(i,"doc-",[i.getEndRule("no_regex")]),this.normalizeRules()};r.inherits(u,s),t.JavaScriptHighlightRules=u}),define("ace/mode/xml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(e){var t="[_:a-zA-Z\u00c0-\uffff][-_:.a-zA-Z0-9\u00c0-\uffff]*";this.$rules={start:[{token:"string.cdata.xml",regex:"<\\!\\[CDATA\\[",next:"cdata"},{token:["punctuation.instruction.xml","keyword.instruction.xml"],regex:"(<\\?)("+t+")",next:"processing_instruction"},{token:"comment.start.xml",regex:"<\\!--",next:"comment"},{token:["xml-pe.doctype.xml","xml-pe.doctype.xml"],regex:"(<\\!)(DOCTYPE)(?=[\\s])",next:"doctype",caseInsensitive:!0},{include:"tag"},{token:"text.end-tag-open.xml",regex:"",next:"start"}],doctype:[{include:"whitespace"},{include:"string"},{token:"xml-pe.doctype.xml",regex:">",next:"start"},{token:"xml-pe.xml",regex:"[-_a-zA-Z0-9:]+"},{token:"punctuation.int-subset",regex:"\\[",push:"int_subset"}],int_subset:[{token:"text.xml",regex:"\\s+"},{token:"punctuation.int-subset.xml",regex:"]",next:"pop"},{token:["punctuation.markup-decl.xml","keyword.markup-decl.xml"],regex:"(<\\!)("+t+")",push:[{token:"text",regex:"\\s+"},{token:"punctuation.markup-decl.xml",regex:">",next:"pop"},{include:"string"}]}],cdata:[{token:"string.cdata.xml",regex:"\\]\\]>",next:"start"},{token:"text.xml",regex:"\\s+"},{token:"text.xml",regex:"(?:[^\\]]|\\](?!\\]>))+"}],comment:[{token:"comment.end.xml",regex:"-->",next:"start"},{defaultToken:"comment.xml"}],reference:[{token:"constant.language.escape.reference.xml",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"}],attr_reference:[{token:"constant.language.escape.reference.attribute-value.xml",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"}],tag:[{token:["meta.tag.punctuation.tag-open.xml","meta.tag.punctuation.end-tag-open.xml","meta.tag.tag-name.xml"],regex:"(?:(<)|(",next:"start"}]}],tag_whitespace:[{token:"text.tag-whitespace.xml",regex:"\\s+"}],whitespace:[{token:"text.whitespace.xml",regex:"\\s+"}],string:[{token:"string.xml",regex:"'",push:[{token:"string.xml",regex:"'",next:"pop"},{defaultToken:"string.xml"}]},{token:"string.xml",regex:'"',push:[{token:"string.xml",regex:'"',next:"pop"},{defaultToken:"string.xml"}]}],attributes:[{token:"entity.other.attribute-name.xml",regex:t},{token:"keyword.operator.attribute-equals.xml",regex:"="},{include:"tag_whitespace"},{include:"attribute_value"}],attribute_value:[{token:"string.attribute-value.xml",regex:"'",push:[{token:"string.attribute-value.xml",regex:"'",next:"pop"},{include:"attr_reference"},{defaultToken:"string.attribute-value.xml"}]},{token:"string.attribute-value.xml",regex:'"',push:[{token:"string.attribute-value.xml",regex:'"',next:"pop"},{include:"attr_reference"},{defaultToken:"string.attribute-value.xml"}]}]},this.constructor===s&&this.normalizeRules()};(function(){this.embedTagRules=function(e,t,n){this.$rules.tag.unshift({token:["meta.tag.punctuation.tag-open.xml","meta.tag."+n+".tag-name.xml"],regex:"(<)("+n+"(?=\\s|>|$))",next:[{include:"attributes"},{token:"meta.tag.punctuation.tag-close.xml",regex:"/?>",next:t+"start"}]}),this.$rules[n+"-end"]=[{include:"attributes"},{token:"meta.tag.punctuation.tag-close.xml",regex:"/?>",next:"start",onMatch:function(e,t,n){return n.splice(0),this.token}}],this.embedRules(e,t,[{token:["meta.tag.punctuation.end-tag-open.xml","meta.tag."+n+".tag-name.xml"],regex:"(|$))",next:n+"-end"},{token:"string.cdata.xml",regex:"<\\!\\[CDATA\\["},{token:"string.cdata.xml",regex:"\\]\\]>"}])}}).call(i.prototype),r.inherits(s,i),t.XmlHighlightRules=s}),define("ace/mode/html_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/css_highlight_rules","ace/mode/javascript_highlight_rules","ace/mode/xml_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./css_highlight_rules").CssHighlightRules,o=e("./javascript_highlight_rules").JavaScriptHighlightRules,u=e("./xml_highlight_rules").XmlHighlightRules,a=i.createMap({a:"anchor",button:"form",form:"form",img:"image",input:"form",label:"form",option:"form",script:"script",select:"form",textarea:"form",style:"style",table:"table",tbody:"table",td:"table",tfoot:"table",th:"table",tr:"table"}),f=function(){u.call(this),this.addRules({attributes:[{include:"tag_whitespace"},{token:"entity.other.attribute-name.xml",regex:"[-_a-zA-Z0-9:.]+"},{token:"keyword.operator.attribute-equals.xml",regex:"=",push:[{include:"tag_whitespace"},{token:"string.unquoted.attribute-value.html",regex:"[^<>='\"`\\s]+",next:"pop"},{token:"empty",regex:"",next:"pop"}]},{include:"attribute_value"}],tag:[{token:function(e,t){var n=a[t];return["meta.tag.punctuation."+(e=="<"?"":"end-")+"tag-open.xml","meta.tag"+(n?"."+n:"")+".tag-name.xml"]},regex:"(",next:"start"}]}),this.embedTagRules(s,"css-","style"),this.embedTagRules((new o({jsx:!1})).getRules(),"js-","script"),this.constructor===f&&this.normalizeRules()};r.inherits(f,u),t.HtmlHighlightRules=f}),define("ace/mode/markdown_highlight_rules",["require","exports","module","ace/config","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules","ace/mode/html_highlight_rules"],function(e,t,n){"use strict";var r=e("../config").$modes,i=e("../lib/oop"),s=e("../lib/lang"),o=e("./text_highlight_rules").TextHighlightRules,u=e("./html_highlight_rules").HtmlHighlightRules,a=function(e){return"(?:[^"+s.escapeRegExp(e)+"\\\\]|\\\\.)*"},f=function(){u.call(this);var e={token:"support.function",regex:/^\s*(```+[^`]*|~~~+[^~]*)$/,onMatch:function(e,t,n,i){var s=e.match(/^(\s*)([`~]+)(.*)/),o=/[\w-]+|$/.exec(s[3])[0];return r[o]||(o=""),n.unshift("githubblock",[],[s[1],s[2],o],t),this.token},next:"githubblock"},t=[{token:"support.function",regex:".*",onMatch:function(e,t,n,i){var s=n[1],o=n[2][0],u=n[2][1],a=n[2][2],f=/^(\s*)(`+|~+)\s*$/.exec(e);if(f&&f[1].length=u.length&&f[2][0]==u[0])return n.splice(0,3),this.next=n.shift(),this.token;this.next="";if(a&&r[a]){var l=r[a].getTokenizer().getLineTokens(e,s.slice(0));return n[1]=l.state,l.tokens}return this.token}}];this.$rules.start.unshift({token:"empty_line",regex:"^$",next:"allowBlock"},{token:"markup.heading.1",regex:"^=+(?=\\s*$)"},{token:"markup.heading.2",regex:"^\\-+(?=\\s*$)"},{token:function(e){return"markup.heading."+e.length},regex:/^#{1,6}(?=\s|$)/,next:"header"},e,{token:"string.blockquote",regex:"^\\s*>\\s*(?:[*+-]|\\d+\\.)?\\s+",next:"blockquote"},{token:"constant",regex:"^ {0,3}(?:(?:\\* ?){3,}|(?:\\- ?){3,}|(?:\\_ ?){3,})\\s*$",next:"allowBlock"},{token:"markup.list",regex:"^\\s{0,3}(?:[*+-]|\\d+\\.)\\s+",next:"listblock-start"},{include:"basic"}),this.addRules({basic:[{token:"constant.language.escape",regex:/\\[\\`*_{}\[\]()#+\-.!]/},{token:"support.function",regex:"(`+)(.*?[^`])(\\1)"},{token:["text","constant","text","url","string","text"],regex:'^([ ]{0,3}\\[)([^\\]]+)(\\]:\\s*)([^ ]+)(\\s*(?:["][^"]+["])?(\\s*))$'},{token:["text","string","text","constant","text"],regex:"(\\[)("+a("]")+")(\\]\\s*\\[)("+a("]")+")(\\])"},{token:["text","string","text","markup.underline","string","text"],regex:"(\\!?\\[)("+a("]")+")(\\]\\()"+'((?:[^\\)\\s\\\\]|\\\\.|\\s(?=[^"]))*)'+'(\\s*"'+a('"')+'"\\s*)?'+"(\\))"},{token:"string.strong",regex:"([*]{2}|[_]{2}(?=\\S))(.*?\\S[*_]*)(\\1)"},{token:"string.emphasis",regex:"([*]|[_](?=\\S))(.*?\\S[*_]*)(\\1)"},{token:["text","url","text"],regex:"(<)((?:https?|ftp|dict):[^'\">\\s]+|(?:mailto:)?[-.\\w]+\\@[-a-z0-9]+(?:\\.[-a-z0-9]+)*\\.[a-z]+)(>)"}],allowBlock:[{token:"support.function",regex:"^ {4}.+",next:"allowBlock"},{token:"empty_line",regex:"^$",next:"allowBlock"},{token:"empty",regex:"",next:"start"}],header:[{regex:"$",next:"start"},{include:"basic"},{defaultToken:"heading"}],"listblock-start":[{token:"support.variable",regex:/(?:\[[ x]\])?/,next:"listblock"}],listblock:[{token:"empty_line",regex:"^$",next:"start"},{token:"markup.list",regex:"^\\s{0,3}(?:[*+-]|\\d+\\.)\\s+",next:"listblock-start"},{include:"basic",noEscape:!0},e,{defaultToken:"list"}],blockquote:[{token:"empty_line",regex:"^\\s*$",next:"start"},{token:"string.blockquote",regex:"^\\s*>\\s*(?:[*+-]|\\d+\\.)?\\s+",next:"blockquote"},{include:"basic",noEscape:!0},{defaultToken:"string.blockquote"}],githubblock:t}),this.normalizeRules()};i.inherits(f,o),t.MarkdownHighlightRules=f}),define("ace/mode/folding/markdown",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=e("../../range").Range,o=t.FoldMode=function(){};r.inherits(o,i),function(){this.foldingStartMarker=/^(?:[=-]+\s*$|#{1,6} |`{3})/,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);return this.foldingStartMarker.test(r)?r[0]=="`"?e.bgTokenizer.getState(n)=="start"?"end":"start":"start":""},this.getFoldWidgetRange=function(e,t,n){function l(t){return f=e.getTokens(t)[0],f&&f.type.lastIndexOf(c,0)===0}function h(){var e=f.value[0];return e=="="?6:e=="-"?5:7-f.value.search(/[^#]|$/)}var r=e.getLine(n),i=r.length,o=e.getLength(),u=n,a=n;if(!r.match(this.foldingStartMarker))return;if(r[0]=="`"){if(e.bgTokenizer.getState(n)!=="start"){while(++n0){r=e.getLine(n);if(r[0]=="`"&r.substring(0,3)=="```")break}return new s(n,r.length,u,0)}var f,c="markup.heading";if(l(n)){var p=h();while(++n=p)break}a=n-(!f||["=","-"].indexOf(f.value[0])==-1?1:2);if(a>u)while(a>u&&/^\s*$/.test(e.getLine(a)))a--;if(a>u){var v=e.getLine(a).length;return new s(u,i,a,v)}}}}.call(o.prototype)}),define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),define("ace/mode/javascript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/javascript_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./javascript_highlight_rules").JavaScriptHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../worker/worker_client").WorkerClient,a=e("./behaviour/cstyle").CstyleBehaviour,f=e("./folding/cstyle").FoldMode,l=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new a,this.foldingRules=new f};r.inherits(l,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.$quotes={'"':'"',"'":"'","`":"`"},this.$pairQuotesAfter={"`":/\w/},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e),s=i.tokens,o=i.state;if(s.length&&s[s.length-1].type=="comment")return r;if(e=="start"||e=="no_regex"){var u=t.match(/^.*(?:\bcase\b.*:|[\{\(\[])\s*$/);u&&(r+=n)}else if(e=="doc-start"){if(o=="start"||o=="no_regex")return"";var u=t.match(/^\s*(\/?)\*/);u&&(u[1]&&(r+=" "),r+="* ")}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.createWorker=function(e){var t=new u(["ace"],"ace/mode/javascript_worker","JavaScriptWorker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/javascript",this.snippetFileId="ace/snippets/javascript"}.call(l.prototype),t.Mode=l}),define("ace/mode/css_completions",["require","exports","module"],function(e,t,n){"use strict";var r={background:{"#$0":1},"background-color":{"#$0":1,transparent:1,fixed:1},"background-image":{"url('/$0')":1},"background-repeat":{repeat:1,"repeat-x":1,"repeat-y":1,"no-repeat":1,inherit:1},"background-position":{bottom:2,center:2,left:2,right:2,top:2,inherit:2},"background-attachment":{scroll:1,fixed:1},"background-size":{cover:1,contain:1},"background-clip":{"border-box":1,"padding-box":1,"content-box":1},"background-origin":{"border-box":1,"padding-box":1,"content-box":1},border:{"solid $0":1,"dashed $0":1,"dotted $0":1,"#$0":1},"border-color":{"#$0":1},"border-style":{solid:2,dashed:2,dotted:2,"double":2,groove:2,hidden:2,inherit:2,inset:2,none:2,outset:2,ridged:2},"border-collapse":{collapse:1,separate:1},bottom:{px:1,em:1,"%":1},clear:{left:1,right:1,both:1,none:1},color:{"#$0":1,"rgb(#$00,0,0)":1},cursor:{"default":1,pointer:1,move:1,text:1,wait:1,help:1,progress:1,"n-resize":1,"ne-resize":1,"e-resize":1,"se-resize":1,"s-resize":1,"sw-resize":1,"w-resize":1,"nw-resize":1},display:{none:1,block:1,inline:1,"inline-block":1,"table-cell":1},"empty-cells":{show:1,hide:1},"float":{left:1,right:1,none:1},"font-family":{Arial:2,"Comic Sans MS":2,Consolas:2,"Courier New":2,Courier:2,Georgia:2,Monospace:2,"Sans-Serif":2,"Segoe UI":2,Tahoma:2,"Times New Roman":2,"Trebuchet MS":2,Verdana:1},"font-size":{px:1,em:1,"%":1},"font-weight":{bold:1,normal:1},"font-style":{italic:1,normal:1},"font-variant":{normal:1,"small-caps":1},height:{px:1,em:1,"%":1},left:{px:1,em:1,"%":1},"letter-spacing":{normal:1},"line-height":{normal:1},"list-style-type":{none:1,disc:1,circle:1,square:1,decimal:1,"decimal-leading-zero":1,"lower-roman":1,"upper-roman":1,"lower-greek":1,"lower-latin":1,"upper-latin":1,georgian:1,"lower-alpha":1,"upper-alpha":1},margin:{px:1,em:1,"%":1},"margin-right":{px:1,em:1,"%":1},"margin-left":{px:1,em:1,"%":1},"margin-top":{px:1,em:1,"%":1},"margin-bottom":{px:1,em:1,"%":1},"max-height":{px:1,em:1,"%":1},"max-width":{px:1,em:1,"%":1},"min-height":{px:1,em:1,"%":1},"min-width":{px:1,em:1,"%":1},overflow:{hidden:1,visible:1,auto:1,scroll:1},"overflow-x":{hidden:1,visible:1,auto:1,scroll:1},"overflow-y":{hidden:1,visible:1,auto:1,scroll:1},padding:{px:1,em:1,"%":1},"padding-top":{px:1,em:1,"%":1},"padding-right":{px:1,em:1,"%":1},"padding-bottom":{px:1,em:1,"%":1},"padding-left":{px:1,em:1,"%":1},"page-break-after":{auto:1,always:1,avoid:1,left:1,right:1},"page-break-before":{auto:1,always:1,avoid:1,left:1,right:1},position:{absolute:1,relative:1,fixed:1,"static":1},right:{px:1,em:1,"%":1},"table-layout":{fixed:1,auto:1},"text-decoration":{none:1,underline:1,"line-through":1,blink:1},"text-align":{left:1,right:1,center:1,justify:1},"text-transform":{capitalize:1,uppercase:1,lowercase:1,none:1},top:{px:1,em:1,"%":1},"vertical-align":{top:1,bottom:1},visibility:{hidden:1,visible:1},"white-space":{nowrap:1,normal:1,pre:1,"pre-line":1,"pre-wrap":1},width:{px:1,em:1,"%":1},"word-spacing":{normal:1},filter:{"alpha(opacity=$0100)":1},"text-shadow":{"$02px 2px 2px #777":1},"text-overflow":{"ellipsis-word":1,clip:1,ellipsis:1},"-moz-border-radius":1,"-moz-border-radius-topright":1,"-moz-border-radius-bottomright":1,"-moz-border-radius-topleft":1,"-moz-border-radius-bottomleft":1,"-webkit-border-radius":1,"-webkit-border-top-right-radius":1,"-webkit-border-top-left-radius":1,"-webkit-border-bottom-right-radius":1,"-webkit-border-bottom-left-radius":1,"-moz-box-shadow":1,"-webkit-box-shadow":1,transform:{"rotate($00deg)":1,"skew($00deg)":1},"-moz-transform":{"rotate($00deg)":1,"skew($00deg)":1},"-webkit-transform":{"rotate($00deg)":1,"skew($00deg)":1}},i=function(){};(function(){this.completionsDefined=!1,this.defineCompletions=function(){if(document){var e=document.createElement("c").style;for(var t in e){if(typeof e[t]!="string")continue;var n=t.replace(/[A-Z]/g,function(e){return"-"+e.toLowerCase()});r.hasOwnProperty(n)||(r[n]=1)}}this.completionsDefined=!0},this.getCompletions=function(e,t,n,r){this.completionsDefined||this.defineCompletions();if(e==="ruleset"||t.$mode.$id=="ace/mode/scss"){var i=t.getLine(n.row).substr(0,n.column),s=/\([^)]*$/.test(i);return s&&(i=i.substr(i.lastIndexOf("(")+1)),/:[^;]+$/.test(i)?(/([\w\-]+):[^:]*$/.test(i),this.getPropertyValueCompletions(e,t,n,r)):this.getPropertyCompletions(e,t,n,r,s)}return[]},this.getPropertyCompletions=function(e,t,n,i,s){s=s||!1;var o=Object.keys(r);return o.map(function(e){return{caption:e,snippet:e+": $0"+(s?"":";"),meta:"property",score:1e6}})},this.getPropertyValueCompletions=function(e,t,n,i){var s=t.getLine(n.row).substr(0,n.column),o=(/([\w\-]+):[^:]*$/.exec(s)||{})[1];if(!o)return[];var u=[];return o in r&&typeof r[o]=="object"&&(u=Object.keys(r[o])),u.map(function(e){return{caption:e,snippet:e,meta:"property value",score:1e6}})}}).call(i.prototype),t.CssCompletions=i}),define("ace/mode/behaviour/css",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/mode/behaviour/cstyle","ace/token_iterator"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("./cstyle").CstyleBehaviour,o=e("../../token_iterator").TokenIterator,u=function(){this.inherit(s),this.add("colon","insertion",function(e,t,n,r,i){if(i===":"&&n.selection.isEmpty()){var s=n.getCursorPosition(),u=new o(r,s.row,s.column),a=u.getCurrentToken();a&&a.value.match(/\s+/)&&(a=u.stepBackward());if(a&&a.type==="support.type"){var f=r.doc.getLine(s.row),l=f.substring(s.column,s.column+1);if(l===":")return{text:"",selection:[1,1]};if(/^(\s+[^;]|\s*$)/.test(f.substring(s.column)))return{text:":;",selection:[1,1]}}}}),this.add("colon","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s===":"){var u=n.getCursorPosition(),a=new o(r,u.row,u.column),f=a.getCurrentToken();f&&f.value.match(/\s+/)&&(f=a.stepBackward());if(f&&f.type==="support.type"){var l=r.doc.getLine(i.start.row),c=l.substring(i.end.column,i.end.column+1);if(c===";")return i.end.column++,i}}}),this.add("semicolon","insertion",function(e,t,n,r,i){if(i===";"&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row),u=o.substring(s.column,s.column+1);if(u===";")return{text:"",selection:[1,1]}}}),this.add("!important","insertion",function(e,t,n,r,i){if(i==="!"&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row);if(/^\s*(;|}|$)/.test(o.substring(s.column)))return{text:"!important",selection:[10,10]}}})};r.inherits(u,s),t.CssBehaviour=u}),define("ace/mode/css",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/css_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/css_completions","ace/mode/behaviour/css","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./css_highlight_rules").CssHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../worker/worker_client").WorkerClient,a=e("./css_completions").CssCompletions,f=e("./behaviour/css").CssBehaviour,l=e("./folding/cstyle").FoldMode,c=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new f,this.$completer=new a,this.foldingRules=new l};r.inherits(c,i),function(){this.foldingRules="cStyle",this.blockComment={start:"/*",end:"*/"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e).tokens;if(i.length&&i[i.length-1].type=="comment")return r;var s=t.match(/^.*\{\s*$/);return s&&(r+=n),r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.getCompletions=function(e,t,n,r){return this.$completer.getCompletions(e,t,n,r)},this.createWorker=function(e){var t=new u(["ace"],"ace/mode/css_worker","Worker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/css",this.snippetFileId="ace/snippets/css"}.call(c.prototype),t.Mode=c}),define("ace/mode/behaviour/xml",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"],function(e,t,n){"use strict";function u(e,t){return e&&e.type.lastIndexOf(t+".xml")>-1}var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("../../token_iterator").TokenIterator,o=e("../../lib/lang"),a=function(){this.add("string_dquotes","insertion",function(e,t,n,r,i){if(i=='"'||i=="'"){var o=i,a=r.doc.getTextRange(n.getSelectionRange());if(a!==""&&a!=="'"&&a!='"'&&n.getWrapBehavioursEnabled())return{text:o+a+o,selection:!1};var f=n.getCursorPosition(),l=r.doc.getLine(f.row),c=l.substring(f.column,f.column+1),h=new s(r,f.row,f.column),p=h.getCurrentToken();if(c==o&&(u(p,"attribute-value")||u(p,"string")))return{text:"",selection:[1,1]};p||(p=h.stepBackward());if(!p)return;while(u(p,"tag-whitespace")||u(p,"whitespace"))p=h.stepBackward();var d=!c||c.match(/\s/);if(u(p,"attribute-equals")&&(d||c==">")||u(p,"decl-attribute-equals")&&(d||c=="?"))return{text:o+o,selection:[1,1]}}}),this.add("string_dquotes","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&(s=='"'||s=="'")){var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u==s)return i.end.column++,i}}),this.add("autoclosing","insertion",function(e,t,n,r,i){if(i==">"){var o=n.getSelectionRange().start,a=new s(r,o.row,o.column),f=a.getCurrentToken()||a.stepBackward();if(!f||!(u(f,"tag-name")||u(f,"tag-whitespace")||u(f,"attribute-name")||u(f,"attribute-equals")||u(f,"attribute-value")))return;if(u(f,"reference.attribute-value"))return;if(u(f,"attribute-value")){var l=a.getCurrentTokenColumn()+f.value.length;if(o.column/.test(r.getLine(o.row).slice(o.column)))return;while(!u(f,"tag-name")){f=a.stepBackward();if(f.value=="<"){f=a.stepForward();break}}var h=a.getCurrentTokenRow(),p=a.getCurrentTokenColumn();if(u(a.stepBackward(),"end-tag-open"))return;var d=f.value;h==o.row&&(d=d.substring(0,o.column-p));if(this.voidElements.hasOwnProperty(d.toLowerCase()))return;return{text:">",selection:[1,1]}}}),this.add("autoindent","insertion",function(e,t,n,r,i){if(i=="\n"){var o=n.getCursorPosition(),u=r.getLine(o.row),a=new s(r,o.row,o.column),f=a.getCurrentToken();if(f&&f.type.indexOf("tag-close")!==-1){if(f.value=="/>")return;while(f&&f.type.indexOf("tag-name")===-1)f=a.stepBackward();if(!f)return;var l=f.value,c=a.getCurrentTokenRow();f=a.stepBackward();if(!f||f.type.indexOf("end-tag")!==-1)return;if(this.voidElements&&!this.voidElements[l]){var h=r.getTokenAt(o.row,o.column+1),u=r.getLine(c),p=this.$getIndent(u),d=p+r.getTabString();return h&&h.value==="-1}var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e,t){s.call(this),this.voidElements=e||{},this.optionalEndTags=r.mixin({},this.voidElements),t&&r.mixin(this.optionalEndTags,t)};r.inherits(o,s);var u=function(){this.tagName="",this.closing=!1,this.selfClosing=!1,this.start={row:0,column:0},this.end={row:0,column:0}};(function(){this.getFoldWidget=function(e,t,n){var r=this._getFirstTagInLine(e,n);return r?r.closing||!r.tagName&&r.selfClosing?t==="markbeginend"?"end":"":!r.tagName||r.selfClosing||this.voidElements.hasOwnProperty(r.tagName.toLowerCase())?"":this._findEndTagInLine(e,n,r.tagName,r.end.column)?"":"start":this.getCommentFoldWidget(e,n)},this.getCommentFoldWidget=function(e,t){return/comment/.test(e.getState(t))&&/";break}}return r}if(a(s,"tag-close"))return r.selfClosing=s.value=="/>",r;r.start.column+=s.value.length}return null},this._findEndTagInLine=function(e,t,n,r){var i=e.getTokens(t),s=0;for(var o=0;o-1}function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();while(i&&!f(i,"tag-name"))i=n.stepBackward();if(i)return i.value}function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();while(i&&!f(i,"attribute-name"))i=n.stepBackward();if(i)return i.value}var r=e("../token_iterator").TokenIterator,i=["accesskey","class","contenteditable","contextmenu","dir","draggable","dropzone","hidden","id","inert","itemid","itemprop","itemref","itemscope","itemtype","lang","spellcheck","style","tabindex","title","translate"],s=["onabort","onblur","oncancel","oncanplay","oncanplaythrough","onchange","onclick","onclose","oncontextmenu","oncuechange","ondblclick","ondrag","ondragend","ondragenter","ondragleave","ondragover","ondragstart","ondrop","ondurationchange","onemptied","onended","onerror","onfocus","oninput","oninvalid","onkeydown","onkeypress","onkeyup","onload","onloadeddata","onloadedmetadata","onloadstart","onmousedown","onmousemove","onmouseout","onmouseover","onmouseup","onmousewheel","onpause","onplay","onplaying","onprogress","onratechange","onreset","onscroll","onseeked","onseeking","onselect","onshow","onstalled","onsubmit","onsuspend","ontimeupdate","onvolumechange","onwaiting"],o=i.concat(s),u={a:{href:1,target:{_blank:1,top:1},ping:1,rel:{nofollow:1,alternate:1,author:1,bookmark:1,help:1,license:1,next:1,noreferrer:1,prefetch:1,prev:1,search:1,tag:1},media:1,hreflang:1,type:1},abbr:{},address:{},area:{shape:1,coords:1,href:1,hreflang:1,alt:1,target:1,media:1,rel:1,ping:1,type:1},article:{pubdate:1},aside:{},audio:{src:1,autobuffer:1,autoplay:{autoplay:1},loop:{loop:1},controls:{controls:1},muted:{muted:1},preload:{auto:1,metadata:1,none:1}},b:{},base:{href:1,target:1},bdi:{},bdo:{},blockquote:{cite:1},body:{onafterprint:1,onbeforeprint:1,onbeforeunload:1,onhashchange:1,onmessage:1,onoffline:1,onpopstate:1,onredo:1,onresize:1,onstorage:1,onundo:1,onunload:1},br:{},button:{autofocus:1,disabled:{disabled:1},form:1,formaction:1,formenctype:1,formmethod:1,formnovalidate:1,formtarget:1,name:1,value:1,type:{button:1,submit:1}},canvas:{width:1,height:1},caption:{},cite:{},code:{},col:{span:1},colgroup:{span:1},command:{type:1,label:1,icon:1,disabled:1,checked:1,radiogroup:1,command:1},data:{},datalist:{},dd:{},del:{cite:1,datetime:1},details:{open:1},dfn:{},dialog:{open:1},div:{},dl:{},dt:{},em:{},embed:{src:1,height:1,width:1,type:1},fieldset:{disabled:1,form:1,name:1},figcaption:{},figure:{},footer:{},form:{"accept-charset":1,action:1,autocomplete:1,enctype:{"multipart/form-data":1,"application/x-www-form-urlencoded":1},method:{get:1,post:1},name:1,novalidate:1,target:{_blank:1,top:1}},h1:{},h2:{},h3:{},h4:{},h5:{},h6:{},head:{},header:{},hr:{},html:{manifest:1},i:{},iframe:{name:1,src:1,height:1,width:1,sandbox:{"allow-same-origin":1,"allow-top-navigation":1,"allow-forms":1,"allow-scripts":1},seamless:{seamless:1}},img:{alt:1,src:1,height:1,width:1,usemap:1,ismap:1},input:{type:{text:1,password:1,hidden:1,checkbox:1,submit:1,radio:1,file:1,button:1,reset:1,image:31,color:1,date:1,datetime:1,"datetime-local":1,email:1,month:1,number:1,range:1,search:1,tel:1,time:1,url:1,week:1},accept:1,alt:1,autocomplete:{on:1,off:1},autofocus:{autofocus:1},checked:{checked:1},disabled:{disabled:1},form:1,formaction:1,formenctype:{"application/x-www-form-urlencoded":1,"multipart/form-data":1,"text/plain":1},formmethod:{get:1,post:1},formnovalidate:{formnovalidate:1},formtarget:{_blank:1,_self:1,_parent:1,_top:1},height:1,list:1,max:1,maxlength:1,min:1,multiple:{multiple:1},name:1,pattern:1,placeholder:1,readonly:{readonly:1},required:{required:1},size:1,src:1,step:1,width:1,files:1,value:1},ins:{cite:1,datetime:1},kbd:{},keygen:{autofocus:1,challenge:{challenge:1},disabled:{disabled:1},form:1,keytype:{rsa:1,dsa:1,ec:1},name:1},label:{form:1,"for":1},legend:{},li:{value:1},link:{href:1,hreflang:1,rel:{stylesheet:1,icon:1},media:{all:1,screen:1,print:1},type:{"text/css":1,"image/png":1,"image/jpeg":1,"image/gif":1},sizes:1},main:{},map:{name:1},mark:{},math:{},menu:{type:1,label:1},meta:{"http-equiv":{"content-type":1},name:{description:1,keywords:1},content:{"text/html; charset=UTF-8":1},charset:1},meter:{value:1,min:1,max:1,low:1,high:1,optimum:1},nav:{},noscript:{href:1},object:{param:1,data:1,type:1,height:1,width:1,usemap:1,name:1,form:1,classid:1},ol:{start:1,reversed:1},optgroup:{disabled:1,label:1},option:{disabled:1,selected:1,label:1,value:1},output:{"for":1,form:1,name:1},p:{},param:{name:1,value:1},pre:{},progress:{value:1,max:1},q:{cite:1},rp:{},rt:{},ruby:{},s:{},samp:{},script:{charset:1,type:{"text/javascript":1},src:1,defer:1,async:1},select:{autofocus:1,disabled:1,form:1,multiple:{multiple:1},name:1,size:1,readonly:{readonly:1}},small:{},source:{src:1,type:1,media:1},span:{},strong:{},style:{type:1,media:{all:1,screen:1,print:1},scoped:1},sub:{},sup:{},svg:{},table:{summary:1},tbody:{},td:{headers:1,rowspan:1,colspan:1},textarea:{autofocus:{autofocus:1},disabled:{disabled:1},form:1,maxlength:1,name:1,placeholder:1,readonly:{readonly:1},required:{required:1},rows:1,cols:1,wrap:{on:1,off:1,hard:1,soft:1}},tfoot:{},th:{headers:1,rowspan:1,colspan:1,scope:1},thead:{},time:{datetime:1},title:{},tr:{},track:{kind:1,src:1,srclang:1,label:1,"default":1},section:{},summary:{},u:{},ul:{},"var":{},video:{src:1,autobuffer:1,autoplay:{autoplay:1},loop:{loop:1},controls:{controls:1},width:1,height:1,poster:1,muted:{muted:1},preload:{auto:1,metadata:1,none:1}},wbr:{}},a=Object.keys(u),h=function(){};(function(){this.getCompletions=function(e,t,n,r){var i=t.getTokenAt(n.row,n.column);if(!i)return[];if(f(i,"tag-name")||f(i,"tag-open")||f(i,"end-tag-open"))return this.getTagCompletions(e,t,n,r);if(f(i,"tag-whitespace")||f(i,"attribute-name"))return this.getAttributeCompletions(e,t,n,r);if(f(i,"attribute-value"))return this.getAttributeValueCompletions(e,t,n,r);var s=t.getLine(n.row).substr(0,n.column);return/&[a-z]*$/i.test(s)?this.getHTMLEntityCompletions(e,t,n,r):[]},this.getTagCompletions=function(e,t,n,r){return a.map(function(e){return{value:e,meta:"tag",score:1e6}})},this.getAttributeCompletions=function(e,t,n,r){var i=l(t,n);if(!i)return[];var s=o;return i in u&&(s=s.concat(Object.keys(u[i]))),s.map(function(e){return{caption:e,snippet:e+'="$0"',meta:"attribute",score:1e6}})},this.getAttributeValueCompletions=function(e,t,n,r){var i=l(t,n),s=c(t,n);if(!i)return[];var o=[];return i in u&&s in u[i]&&typeof u[i][s]=="object"&&(o=Object.keys(u[i][s])),o.map(function(e){return{caption:e,snippet:e,meta:"attribute value",score:1e6}})},this.getHTMLEntityCompletions=function(e,t,n,r){var i=["Aacute;","aacute;","Acirc;","acirc;","acute;","AElig;","aelig;","Agrave;","agrave;","alefsym;","Alpha;","alpha;","amp;","and;","ang;","Aring;","aring;","asymp;","Atilde;","atilde;","Auml;","auml;","bdquo;","Beta;","beta;","brvbar;","bull;","cap;","Ccedil;","ccedil;","cedil;","cent;","Chi;","chi;","circ;","clubs;","cong;","copy;","crarr;","cup;","curren;","Dagger;","dagger;","dArr;","darr;","deg;","Delta;","delta;","diams;","divide;","Eacute;","eacute;","Ecirc;","ecirc;","Egrave;","egrave;","empty;","emsp;","ensp;","Epsilon;","epsilon;","equiv;","Eta;","eta;","ETH;","eth;","Euml;","euml;","euro;","exist;","fnof;","forall;","frac12;","frac14;","frac34;","frasl;","Gamma;","gamma;","ge;","gt;","hArr;","harr;","hearts;","hellip;","Iacute;","iacute;","Icirc;","icirc;","iexcl;","Igrave;","igrave;","image;","infin;","int;","Iota;","iota;","iquest;","isin;","Iuml;","iuml;","Kappa;","kappa;","Lambda;","lambda;","lang;","laquo;","lArr;","larr;","lceil;","ldquo;","le;","lfloor;","lowast;","loz;","lrm;","lsaquo;","lsquo;","lt;","macr;","mdash;","micro;","middot;","minus;","Mu;","mu;","nabla;","nbsp;","ndash;","ne;","ni;","not;","notin;","nsub;","Ntilde;","ntilde;","Nu;","nu;","Oacute;","oacute;","Ocirc;","ocirc;","OElig;","oelig;","Ograve;","ograve;","oline;","Omega;","omega;","Omicron;","omicron;","oplus;","or;","ordf;","ordm;","Oslash;","oslash;","Otilde;","otilde;","otimes;","Ouml;","ouml;","para;","part;","permil;","perp;","Phi;","phi;","Pi;","pi;","piv;","plusmn;","pound;","Prime;","prime;","prod;","prop;","Psi;","psi;","quot;","radic;","rang;","raquo;","rArr;","rarr;","rceil;","rdquo;","real;","reg;","rfloor;","Rho;","rho;","rlm;","rsaquo;","rsquo;","sbquo;","Scaron;","scaron;","sdot;","sect;","shy;","Sigma;","sigma;","sigmaf;","sim;","spades;","sub;","sube;","sum;","sup;","sup1;","sup2;","sup3;","supe;","szlig;","Tau;","tau;","there4;","Theta;","theta;","thetasym;","thinsp;","THORN;","thorn;","tilde;","times;","trade;","Uacute;","uacute;","uArr;","uarr;","Ucirc;","ucirc;","Ugrave;","ugrave;","uml;","upsih;","Upsilon;","upsilon;","Uuml;","uuml;","weierp;","Xi;","xi;","Yacute;","yacute;","yen;","Yuml;","yuml;","Zeta;","zeta;","zwj;","zwnj;"];return i.map(function(e){return{caption:e,snippet:e,meta:"html entity",score:1e6}})}}).call(h.prototype),t.HtmlCompletions=h}),define("ace/mode/html",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text","ace/mode/javascript","ace/mode/css","ace/mode/html_highlight_rules","ace/mode/behaviour/xml","ace/mode/folding/html","ace/mode/html_completions","ace/worker/worker_client"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text").Mode,o=e("./javascript").Mode,u=e("./css").Mode,a=e("./html_highlight_rules").HtmlHighlightRules,f=e("./behaviour/xml").XmlBehaviour,l=e("./folding/html").FoldMode,c=e("./html_completions").HtmlCompletions,h=e("../worker/worker_client").WorkerClient,p=["area","base","br","col","embed","hr","img","input","keygen","link","meta","menuitem","param","source","track","wbr"],d=["li","dt","dd","p","rt","rp","optgroup","option","colgroup","td","th"],v=function(e){this.fragmentContext=e&&e.fragmentContext,this.HighlightRules=a,this.$behaviour=new f,this.$completer=new c,this.createModeDelegates({"js-":o,"css-":u}),this.foldingRules=new l(this.voidElements,i.arrayToMap(d))};r.inherits(v,s),function(){this.blockComment={start:""},this.voidElements=i.arrayToMap(p),this.getNextLineIndent=function(e,t,n){return this.$getIndent(t)},this.checkOutdent=function(e,t,n){return!1},this.getCompletions=function(e,t,n,r){return this.$completer.getCompletions(e,t,n,r)},this.createWorker=function(e){if(this.constructor!=v)return;var t=new h(["ace"],"ace/mode/html_worker","Worker");return t.attachToDocument(e.getDocument()),this.fragmentContext&&t.call("setOptions",[{context:this.fragmentContext}]),t.on("error",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/html",this.snippetFileId="ace/snippets/html"}.call(v.prototype),t.Mode=v}),define("ace/mode/sh_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=t.reservedKeywords="!|{|}|case|do|done|elif|else|esac|fi|for|if|in|then|until|while|&|;|export|local|read|typeset|unset|elif|select|set|function|declare|readonly",o=t.languageConstructs="[|]|alias|bg|bind|break|builtin|cd|command|compgen|complete|continue|dirs|disown|echo|enable|eval|exec|exit|fc|fg|getopts|hash|help|history|jobs|kill|let|logout|popd|printf|pushd|pwd|return|set|shift|shopt|source|suspend|test|times|trap|type|ulimit|umask|unalias|wait",u=function(){var e=this.createKeywordMapper({keyword:s,"support.function.builtin":o,"invalid.deprecated":"debugger"},"identifier"),t="(?:(?:[1-9]\\d*)|(?:0))",n="(?:\\.\\d+)",r="(?:\\d+)",i="(?:(?:"+r+"?"+n+")|(?:"+r+"\\.))",u="(?:(?:"+i+"|"+r+")"+")",a="(?:"+u+"|"+i+")",f="(?:&"+r+")",l="[a-zA-Z_][a-zA-Z0-9_]*",c="(?:"+l+"(?==))",h="(?:\\$(?:SHLVL|\\$|\\!|\\?))",p="(?:"+l+"\\s*\\(\\))";this.$rules={start:[{token:"constant",regex:/\\./},{token:["text","comment"],regex:/(^|\s)(#.*)$/},{token:"string.start",regex:'"',push:[{token:"constant.language.escape",regex:/\\(?:[$`"\\]|$)/},{include:"variables"},{token:"keyword.operator",regex:/`/},{token:"string.end",regex:'"',next:"pop"},{defaultToken:"string"}]},{token:"string",regex:"\\$'",push:[{token:"constant.language.escape",regex:/\\(?:[abeEfnrtv\\'"]|x[a-fA-F\d]{1,2}|u[a-fA-F\d]{4}([a-fA-F\d]{4})?|c.|\d{1,3})/},{token:"string",regex:"'",next:"pop"},{defaultToken:"string"}]},{regex:"<<<",token:"keyword.operator"},{stateName:"heredoc",regex:"(<<-?)(\\s*)(['\"`]?)([\\w\\-]+)(['\"`]?)",onMatch:function(e,t,n){var r=e[2]=="-"?"indentedHeredoc":"heredoc",i=e.split(this.splitRegex);return n.push(r,i[4]),[{type:"constant",value:i[1]},{type:"text",value:i[2]},{type:"string",value:i[3]},{type:"support.class",value:i[4]},{type:"string",value:i[5]}]},rules:{heredoc:[{onMatch:function(e,t,n){return e===n[1]?(n.shift(),n.shift(),this.next=n[0]||"start","support.class"):(this.next="","string")},regex:".*$",next:"start"}],indentedHeredoc:[{token:"string",regex:"^ +"},{onMatch:function(e,t,n){return e===n[1]?(n.shift(),n.shift(),this.next=n[0]||"start","support.class"):(this.next="","string")},regex:".*$",next:"start"}]}},{regex:"$",token:"empty",next:function(e,t){return t[0]==="heredoc"||t[0]==="indentedHeredoc"?t[0]:e}},{token:["keyword","text","text","text","variable"],regex:/(declare|local|readonly)(\s+)(?:(-[fixar]+)(\s+))?([a-zA-Z_][a-zA-Z0-9_]*\b)/},{token:"variable.language",regex:h},{token:"variable",regex:c},{include:"variables"},{token:"support.function",regex:p},{token:"support.function",regex:f},{token:"string",start:"'",end:"'"},{token:"constant.numeric",regex:a},{token:"constant.numeric",regex:t+"\\b"},{token:e,regex:"[a-zA-Z_][a-zA-Z0-9_]*\\b"},{token:"keyword.operator",regex:"\\+|\\-|\\*|\\*\\*|\\/|\\/\\/|~|<|>|<=|=>|=|!=|[%&|`]"},{token:"punctuation.operator",regex:";"},{token:"paren.lparen",regex:"[\\[\\(\\{]"},{token:"paren.rparen",regex:"[\\]]"},{token:"paren.rparen",regex:"[\\)\\}]",next:"pop"}],variables:[{token:"variable",regex:/(\$)(\w+)/},{token:["variable","paren.lparen"],regex:/(\$)(\()/,push:"start"},{token:["variable","paren.lparen","keyword.operator","variable","keyword.operator"],regex:/(\$)(\{)([#!]?)(\w+|[*@#?\-$!0_])(:[?+\-=]?|##?|%%?|,,?\/|\^\^?)?/,push:"start"},{token:"variable",regex:/\$[*@#?\-$!0_]/},{token:["variable","paren.lparen"],regex:/(\$)(\{)/,push:"start"}]},this.normalizeRules()};r.inherits(u,i),t.ShHighlightRules=u}),define("ace/mode/sh",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/sh_highlight_rules","ace/range","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./sh_highlight_rules").ShHighlightRules,o=e("../range").Range,u=e("./folding/cstyle").FoldMode,a=function(){this.HighlightRules=s,this.foldingRules=new u,this.$behaviour=this.$defaultBehaviour};r.inherits(a,i),function(){this.lineCommentStart="#",this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e),s=i.tokens;if(s.length&&s[s.length-1].type=="comment")return r;if(e=="start"){var o=t.match(/^.*[\{\(\[:]\s*$/);o&&(r+=n)}return r};var e={pass:1,"return":1,raise:1,"break":1,"continue":1};this.checkOutdent=function(t,n,r){if(r!=="\r\n"&&r!=="\r"&&r!=="\n")return!1;var i=this.getTokenizer().getLineTokens(n.trim(),t).tokens;if(!i)return!1;do var s=i.pop();while(s&&(s.type=="comment"||s.type=="text"&&s.value.match(/^\s+$/)));return s?s.type=="keyword"&&e[s.value]:!1},this.autoOutdent=function(e,t,n){n+=1;var r=this.$getIndent(t.getLine(n)),i=t.getTabString();r.slice(-i.length)==i&&t.remove(new o(n,r.length-i.length,n,r.length))},this.$id="ace/mode/sh",this.snippetFileId="ace/snippets/sh"}.call(a.prototype),t.Mode=a}),define("ace/mode/xml",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text","ace/mode/xml_highlight_rules","ace/mode/behaviour/xml","ace/mode/folding/xml","ace/worker/worker_client"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text").Mode,o=e("./xml_highlight_rules").XmlHighlightRules,u=e("./behaviour/xml").XmlBehaviour,a=e("./folding/xml").FoldMode,f=e("../worker/worker_client").WorkerClient,l=function(){this.HighlightRules=o,this.$behaviour=new u,this.foldingRules=new a};r.inherits(l,s),function(){this.voidElements=i.arrayToMap([]),this.blockComment={start:""},this.createWorker=function(e){var t=new f(["ace"],"ace/mode/xml_worker","Worker");return t.attachToDocument(e.getDocument()),t.on("error",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/xml"}.call(l.prototype),t.Mode=l}),define("ace/mode/markdown",["require","exports","module","ace/lib/oop","ace/mode/behaviour/cstyle","ace/mode/text","ace/mode/markdown_highlight_rules","ace/mode/folding/markdown","ace/mode/javascript","ace/mode/html","ace/mode/sh","ace/mode/sh","ace/mode/xml","ace/mode/css"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./behaviour/cstyle").CstyleBehaviour,s=e("./text").Mode,o=e("./markdown_highlight_rules").MarkdownHighlightRules,u=e("./folding/markdown").FoldMode,a=function(){this.HighlightRules=o,this.createModeDelegates({javascript:e("./javascript").Mode,html:e("./html").Mode,bash:e("./sh").Mode,sh:e("./sh").Mode,xml:e("./xml").Mode,css:e("./css").Mode}),this.foldingRules=new u,this.$behaviour=new i({braces:!0})};r.inherits(a,s),function(){this.type="text",this.blockComment={start:""},this.$quotes={'"':'"',"`":"`"},this.getNextLineIndent=function(e,t,n){if(e=="listblock"){var r=/^(\s*)(?:([-+*])|(\d+)\.)(\s+)/.exec(t);if(!r)return"";var i=r[2];return i||(i=parseInt(r[3],10)+1+"."),r[1]+i+r[4]}return this.$getIndent(t)},this.$id="ace/mode/markdown",this.snippetFileId="ace/snippets/markdown"}.call(a.prototype),t.Mode=a}); (function() { + window.require(["ace/mode/markdown"], function(m) { + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = m; + } + }); + })(); + \ No newline at end of file diff --git a/static/js/ace/theme-github.js b/static/js/ace/theme-github.js new file mode 100644 index 0000000..10c6ab5 --- /dev/null +++ b/static/js/ace/theme-github.js @@ -0,0 +1,8 @@ +define("ace/theme/github-css",["require","exports","module"],function(e,t,n){n.exports='/* CSS style content from github\'s default pygments highlighter template.\n Cursor and selection styles from textmate.css. */\n.ace-github .ace_gutter {\n background: #e8e8e8;\n color: #AAA;\n}\n\n.ace-github {\n background: #fff;\n color: #000;\n}\n\n.ace-github .ace_keyword {\n font-weight: bold;\n}\n\n.ace-github .ace_string {\n color: #D14;\n}\n\n.ace-github .ace_variable.ace_class {\n color: teal;\n}\n\n.ace-github .ace_constant.ace_numeric {\n color: #099;\n}\n\n.ace-github .ace_constant.ace_buildin {\n color: #0086B3;\n}\n\n.ace-github .ace_support.ace_function {\n color: #0086B3;\n}\n\n.ace-github .ace_comment {\n color: #998;\n font-style: italic;\n}\n\n.ace-github .ace_variable.ace_language {\n color: #0086B3;\n}\n\n.ace-github .ace_paren {\n font-weight: bold;\n}\n\n.ace-github .ace_boolean {\n font-weight: bold;\n}\n\n.ace-github .ace_string.ace_regexp {\n color: #009926;\n font-weight: normal;\n}\n\n.ace-github .ace_variable.ace_instance {\n color: teal;\n}\n\n.ace-github .ace_constant.ace_language {\n font-weight: bold;\n}\n\n.ace-github .ace_cursor {\n color: black;\n}\n\n.ace-github.ace_focus .ace_marker-layer .ace_active-line {\n background: rgb(255, 255, 204);\n}\n.ace-github .ace_marker-layer .ace_active-line {\n background: rgb(245, 245, 245);\n}\n\n.ace-github .ace_marker-layer .ace_selection {\n background: rgb(181, 213, 255);\n}\n\n.ace-github.ace_multiselect .ace_selection.ace_start {\n box-shadow: 0 0 3px 0px white;\n}\n/* bold keywords cause cursor issues for some fonts */\n/* this disables bold style for editor and keeps for static highlighter */\n.ace-github.ace_nobold .ace_line > span {\n font-weight: normal !important;\n}\n\n.ace-github .ace_marker-layer .ace_step {\n background: rgb(252, 255, 0);\n}\n\n.ace-github .ace_marker-layer .ace_stack {\n background: rgb(164, 229, 101);\n}\n\n.ace-github .ace_marker-layer .ace_bracket {\n margin: -1px 0 0 -1px;\n border: 1px solid rgb(192, 192, 192);\n}\n\n.ace-github .ace_gutter-active-line {\n background-color : rgba(0, 0, 0, 0.07);\n}\n\n.ace-github .ace_marker-layer .ace_selected-word {\n background: rgb(250, 250, 255);\n border: 1px solid rgb(200, 200, 250);\n}\n\n.ace-github .ace_invisible {\n color: #BFBFBF\n}\n\n.ace-github .ace_print-margin {\n width: 1px;\n background: #e8e8e8;\n}\n\n.ace-github .ace_indent-guide {\n background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;\n}\n\n.ace-github .ace_indent-guide-active {\n background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAAZSURBVHjaYvj///9/hivKyv8BAAAA//8DACLqBhbvk+/eAAAAAElFTkSuQmCC") right repeat-y;\n}\n'}),define("ace/theme/github",["require","exports","module","ace/theme/github-css","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-github",t.cssText=e("./github-css");var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass,!1)}); (function() { + window.require(["ace/theme/github"], function(m) { + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = m; + } + }); + })(); + \ No newline at end of file diff --git a/static/js/ace/theme-monokai.js b/static/js/ace/theme-monokai.js new file mode 100644 index 0000000..a5f5040 --- /dev/null +++ b/static/js/ace/theme-monokai.js @@ -0,0 +1,8 @@ +define("ace/theme/monokai-css",["require","exports","module"],function(e,t,n){n.exports=".ace-monokai .ace_gutter {\n background: #2F3129;\n color: #8F908A\n}\n\n.ace-monokai .ace_print-margin {\n width: 1px;\n background: #555651\n}\n\n.ace-monokai {\n background-color: #272822;\n color: #F8F8F2\n}\n\n.ace-monokai .ace_cursor {\n color: #F8F8F0\n}\n\n.ace-monokai .ace_marker-layer .ace_selection {\n background: #49483E\n}\n\n.ace-monokai.ace_multiselect .ace_selection.ace_start {\n box-shadow: 0 0 3px 0px #272822;\n}\n\n.ace-monokai .ace_marker-layer .ace_step {\n background: rgb(102, 82, 0)\n}\n\n.ace-monokai .ace_marker-layer .ace_bracket {\n margin: -1px 0 0 -1px;\n border: 1px solid #49483E\n}\n\n.ace-monokai .ace_marker-layer .ace_active-line {\n background: #202020\n}\n\n.ace-monokai .ace_gutter-active-line {\n background-color: #272727\n}\n\n.ace-monokai .ace_marker-layer .ace_selected-word {\n border: 1px solid #49483E\n}\n\n.ace-monokai .ace_invisible {\n color: #52524d\n}\n\n.ace-monokai .ace_entity.ace_name.ace_tag,\n.ace-monokai .ace_keyword,\n.ace-monokai .ace_meta.ace_tag,\n.ace-monokai .ace_storage {\n color: #F92672\n}\n\n.ace-monokai .ace_punctuation,\n.ace-monokai .ace_punctuation.ace_tag {\n color: #fff\n}\n\n.ace-monokai .ace_constant.ace_character,\n.ace-monokai .ace_constant.ace_language,\n.ace-monokai .ace_constant.ace_numeric,\n.ace-monokai .ace_constant.ace_other {\n color: #AE81FF\n}\n\n.ace-monokai .ace_invalid {\n color: #F8F8F0;\n background-color: #F92672\n}\n\n.ace-monokai .ace_invalid.ace_deprecated {\n color: #F8F8F0;\n background-color: #AE81FF\n}\n\n.ace-monokai .ace_support.ace_constant,\n.ace-monokai .ace_support.ace_function {\n color: #66D9EF\n}\n\n.ace-monokai .ace_fold {\n background-color: #A6E22E;\n border-color: #F8F8F2\n}\n\n.ace-monokai .ace_storage.ace_type,\n.ace-monokai .ace_support.ace_class,\n.ace-monokai .ace_support.ace_type {\n font-style: italic;\n color: #66D9EF\n}\n\n.ace-monokai .ace_entity.ace_name.ace_function,\n.ace-monokai .ace_entity.ace_other,\n.ace-monokai .ace_entity.ace_other.ace_attribute-name,\n.ace-monokai .ace_variable {\n color: #A6E22E\n}\n\n.ace-monokai .ace_variable.ace_parameter {\n font-style: italic;\n color: #FD971F\n}\n\n.ace-monokai .ace_string {\n color: #E6DB74\n}\n\n.ace-monokai .ace_comment {\n color: #75715E\n}\n\n.ace-monokai .ace_indent-guide {\n background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y\n}\n\n.ace-monokai .ace_indent-guide-active {\n background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQIW2PQ1dX9zzBz5sz/ABCcBFFentLlAAAAAElFTkSuQmCC) right repeat-y;\n}\n"}),define("ace/theme/monokai",["require","exports","module","ace/theme/monokai-css","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-monokai",t.cssText=e("./monokai-css");var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass,!1)}); (function() { + window.require(["ace/theme/monokai"], function(m) { + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = m; + } + }); + })(); + \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html index 54da97b..e6d13d5 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -132,6 +132,7 @@

{{ g.user.username }}

  • 📊Dashboard
  • 📋Task Management
  • 🏃‍♂️Sprints
  • +
  • 📝Notes
  • 📊Analytics
  • diff --git a/templates/note_editor.html b/templates/note_editor.html new file mode 100644 index 0000000..f2364e6 --- /dev/null +++ b/templates/note_editor.html @@ -0,0 +1,600 @@ +{% extends "layout.html" %} + +{% block content %} +
    +
    +

    {% if note %}Edit Note{% else %}Create Note{% endif %}

    +
    + Cancel +
    +
    + +
    +
    + +
    +
    + + +
    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    +
    + +
    + + +
    + +
    + +
    + + + + + + + + + + + + + + + + +
    +
    {{ note.content if note else '' }}
    + +
    + +
    + + Cancel +
    +
    + + +
    +

    Preview

    +
    +

    Start typing to see the preview...

    +
    +
    +
    +
    + + {% if note and note.linked_notes %} +
    +

    Linked Notes

    +
    + {% for link in note.linked_notes %} +
    + + {{ link.target_note.title }} + + {{ link.link_type }} +
    + {% endfor %} +
    +
    + {% endif %} +
    + + + + + + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/note_view.html b/templates/note_view.html new file mode 100644 index 0000000..d6f2b86 --- /dev/null +++ b/templates/note_view.html @@ -0,0 +1,566 @@ +{% extends "layout.html" %} + +{% block content %} +
    +
    +
    +

    {{ note.title }}

    +
    + + {% if note.visibility.value == 'Private' %}🔒{% elif note.visibility.value == 'Team' %}👥{% else %}🏢{% endif %} + {{ note.visibility.value }} + + By {{ note.created_by.username }} + Created {{ note.created_at|format_date }} + {% if note.updated_at > note.created_at %} + · Updated {{ note.updated_at|format_date }} + {% endif %} +
    +
    + +
    + {% if note.can_user_edit(g.user) %} + Edit +
    + +
    + {% endif %} + Back to Notes +
    +
    + +
    + {% if note.project %} + + {% endif %} + + {% if note.task %} + + {% endif %} + + {% if note.tags %} +
    + Tags: + + {% for tag in note.tags %} + {{ tag }} + {% endfor %} + +
    + {% endif %} +
    + +
    + {{ note.render_html()|safe }} +
    + + {% if note.linked_notes or note.can_user_edit(g.user) %} +
    +
    +

    Linked Notes

    + {% if note.can_user_edit(g.user) %} + + {% endif %} +
    + + {% if outgoing_links or incoming_links %} +
    + {% for link in outgoing_links %} +
    +
    +

    {{ link.target_note.title }}

    + → {{ link.link_type }} +
    +
    + {{ link.target_note.get_preview()|safe }} +
    + {% if note.can_user_edit(g.user) %} + + {% endif %} +
    + {% endfor %} + {% for link in incoming_links %} +
    +
    +

    {{ link.source_note.title }}

    + ← {{ link.link_type }} +
    +
    + {{ link.source_note.get_preview()|safe }} +
    + {% if note.can_user_edit(g.user) %} + + {% endif %} +
    + {% endfor %} +
    + {% else %} + + {% endif %} +
    + {% endif %} +
    + + +{% if note.can_user_edit(g.user) %} + +{% endif %} + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/notes_list.html b/templates/notes_list.html new file mode 100644 index 0000000..cc56834 --- /dev/null +++ b/templates/notes_list.html @@ -0,0 +1,371 @@ +{% extends "layout.html" %} + +{% block content %} +
    +
    +

    Notes

    + +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + + +
    +
    +
    +
    + + {% if notes %} +
    + {% for note in notes %} +
    +
    +

    + {{ note.title }} +

    +
    + + {% if note.visibility.value == 'Private' %}🔒{% elif note.visibility.value == 'Team' %}👥{% else %}🏢{% endif %} + {{ note.visibility.value }} + + {% if note.updated_at > note.created_at %} + Updated {{ note.updated_at|format_date }} + {% else %} + Created {{ note.created_at|format_date }} + {% endif %} +
    +
    + +
    + {{ note.get_preview()|safe }} +
    + + + +
    + View + {% if note.can_user_edit(g.user) %} + Edit +
    + +
    + {% endif %} +
    +
    + {% endfor %} +
    + + + {% else %} +
    +

    No notes found. Create your first note.

    +
    + {% endif %} +
    + + + +{% endblock %} \ No newline at end of file From 11b25ca867431d0cc131f846800c3e67c9e3ba29 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Sun, 6 Jul 2025 20:22:55 +0200 Subject: [PATCH 02/22] Layout changes. --- templates/note_editor.html | 440 +++++++++++++++++++++++++++++++------ 1 file changed, 377 insertions(+), 63 deletions(-) diff --git a/templates/note_editor.html b/templates/note_editor.html index f2364e6..623cff8 100644 --- a/templates/note_editor.html +++ b/templates/note_editor.html @@ -3,72 +3,78 @@ {% block content %}
    -

    {% if note %}Edit Note{% else %}Create Note{% endif %}

    +

    {% if note %}Edit: {{ note.title }}{% else %}New Note{% endif %}

    - Cancel +
    -
    -
    - -
    -
    - - -
    - -
    -
    - - -
    + +
    +
    +

    Note Settings

    + +
    + + +
    -
    - - -
    +
    + + +
    -
    - - -
    -
    +
    + + +
    -
    - - -
    +
    + + +
    +
    +
    + +
    + +
    + +
    -
    -
    -

    Preview

    +
    +
    +

    Preview

    + +

    Start typing to see the preview...

    @@ -167,6 +178,83 @@

    Linked Notes

    justify-content: space-between; align-items: center; margin-bottom: 2rem; + position: relative; +} + +.editor-actions { + display: flex; + align-items: center; + gap: 1rem; +} + +.btn-icon { + width: 40px; + height: 40px; + border: 1px solid #dee2e6; + background: white; + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + padding: 0; +} + +.btn-icon:hover { + background: #f8f9fa; + border-color: #adb5bd; +} + +.btn-icon:active { + background: #e9ecef; +} + +.btn-icon svg { + color: #666; +} + +.settings-dropdown { + position: fixed; + width: 320px; + background: white; + border: 1px solid #dee2e6; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 1000; + display: none; + max-height: 80vh; + overflow-y: auto; +} + +.settings-dropdown.show { + display: block; +} + +.settings-content { + padding: 1.5rem; +} + +.settings-content h3 { + margin: 0 0 1.5rem 0; + font-size: 1.1rem; + color: #333; +} + +.settings-group { + margin-bottom: 1.25rem; +} + +.settings-group:last-child { + margin-bottom: 0; +} + +.settings-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #555; + font-size: 0.9rem; } .editor-layout { @@ -174,6 +262,11 @@

    Linked Notes

    grid-template-columns: 1fr 1fr; gap: 2rem; min-height: 600px; + transition: grid-template-columns 0.3s ease; +} + +.editor-layout.preview-collapsed { + grid-template-columns: 1fr 60px; } .editor-panel, .preview-panel { @@ -185,14 +278,70 @@

    Linked Notes

    .preview-panel { background: #f8f9fa; + position: relative; + overflow: hidden; + transition: all 0.3s ease; +} + +.preview-panel.collapsed { + cursor: pointer; +} + +.preview-panel.collapsed .markdown-content { + display: none; } -.preview-panel h3 { - margin-top: 0; +.preview-header { + display: flex; + justify-content: space-between; + align-items: center; margin-bottom: 1rem; +} + +.preview-header h3 { + margin: 0; + color: #666; + transition: transform 0.3s ease; +} + +.preview-panel.collapsed .preview-header { + font-size: small; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(-90deg); + width: max-content; +} + +.preview-panel.collapsed .preview-header h3 { + margin: 0; +} + +.collapse-btn { + transition: transform 0.3s ease; + z-index: 10; + position: relative; +} + +.preview-panel.collapsed .collapse-btn { + display: none; +} + +.collapse-icon { + font-size: 16px; + font-weight: normal; + font-family: monospace; + display: inline-block; + transition: transform 0.3s ease; + line-height: 1; + letter-spacing: -2px; color: #666; } +.editor-layout.preview-collapsed .collapse-icon { + transform: rotate(180deg); +} + .form-row { display: grid; grid-template-columns: repeat(3, 1fr); @@ -416,21 +565,49 @@

    Linked Notes

    font-style: italic; } +/* Form elements inside settings dropdown */ +.settings-dropdown .form-control { + font-size: 0.9rem; + padding: 0.5rem 0.75rem; +} + /* Responsive design */ @media (max-width: 1024px) { .editor-layout { grid-template-columns: 1fr; } + .editor-layout.preview-collapsed { + grid-template-columns: 1fr; + } + .preview-panel { order: -1; min-height: 300px; max-height: 400px; } + .preview-panel.collapsed { + min-height: 60px; + max-height: 60px; + } + + .preview-panel.collapsed .preview-header { + position: static; + transform: none; + } + + .preview-panel.collapsed .collapse-btn { + display: flex; + } + .form-row { grid-template-columns: 1fr; } + + .settings-dropdown { + width: 300px; + } } @@ -476,12 +653,43 @@

    Linked Notes

    syncContentAndUpdatePreview(); } +// Extract title from first line of content +function extractTitleFromContent(content) { + const lines = content.split('\n'); + let firstLine = ''; + + // Find the first non-empty line + for (let line of lines) { + const trimmed = line.trim(); + if (trimmed) { + // Remove markdown headers if present + firstLine = trimmed.replace(/^#+\s*/, ''); + break; + } + } + + // If no non-empty line found, use default + return firstLine || 'Untitled Note'; +} + // Sync Ace Editor content with hidden textarea and update preview function syncContentAndUpdatePreview() { if (!aceEditor) return; const content = aceEditor.getValue(); document.getElementById('content').value = content; + + // Update title from first line + const title = extractTitleFromContent(content); + document.getElementById('title').value = title; + + // Update the page header to show current title + const headerTitle = document.querySelector('.editor-header h2'); + if (headerTitle) { + const isEdit = headerTitle.textContent.includes('Edit'); + headerTitle.textContent = title ? (isEdit ? `Edit: ${title}` : title) : (isEdit ? 'Edit Note' : 'Create Note'); + } + updatePreview(); } @@ -549,6 +757,12 @@

    Linked Notes

    const initialContent = document.getElementById('content').value; aceEditor.setValue(initialContent, -1); // -1 moves cursor to start + // If editing and has content, extract title + if (initialContent) { + const title = extractTitleFromContent(initialContent); + document.getElementById('title').value = title; + } + // Listen for changes in Ace Editor aceEditor.on('change', function() { syncContentAndUpdatePreview(); @@ -562,8 +776,8 @@

    Linked Notes

    submitBtn.textContent = 'Saving...'; }); - // Set focus to title field initially - document.getElementById('title').focus(); + // Set focus to ace editor + aceEditor.focus(); } // Initialize when DOM is ready @@ -594,6 +808,106 @@

    Linked Notes

    exec: function() { insertMarkdown('[', '](url)'); } }); } + + // Settings dropdown toggle + const settingsBtn = document.getElementById('settings-toggle'); + const settingsDropdown = document.getElementById('settings-dropdown'); + + function positionDropdown() { + const btnRect = settingsBtn.getBoundingClientRect(); + const dropdownWidth = 320; + const dropdownHeight = settingsDropdown.offsetHeight; + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + // Calculate position + let left = btnRect.right - dropdownWidth; + let top = btnRect.bottom + 8; + + // Adjust if it goes off the right edge + if (left + dropdownWidth > viewportWidth - 20) { + left = viewportWidth - dropdownWidth - 20; + } + + // Adjust if it goes off the left edge + if (left < 20) { + left = 20; + } + + // Adjust if it goes off the bottom + if (top + dropdownHeight > viewportHeight - 20) { + // Show above the button instead + top = btnRect.top - dropdownHeight - 8; + } + + settingsDropdown.style.left = left + 'px'; + settingsDropdown.style.top = top + 'px'; + } + + settingsBtn.addEventListener('click', function(e) { + e.stopPropagation(); + const isShowing = settingsDropdown.classList.contains('show'); + + if (!isShowing) { + settingsDropdown.classList.add('show'); + // Position after showing to get correct dimensions + positionDropdown(); + } else { + settingsDropdown.classList.remove('show'); + } + }); + + // Reposition on window resize + window.addEventListener('resize', function() { + if (settingsDropdown.classList.contains('show')) { + positionDropdown(); + } + }); + + // Close settings dropdown when clicking outside + document.addEventListener('click', function(e) { + if (!settingsDropdown.contains(e.target) && !settingsBtn.contains(e.target)) { + settingsDropdown.classList.remove('show'); + } + }); + + // Prevent dropdown from closing when clicking inside + settingsDropdown.addEventListener('click', function(e) { + e.stopPropagation(); + }); + + // Preview panel collapse/expand + const previewToggle = document.getElementById('preview-toggle'); + const previewPanel = document.getElementById('preview-panel'); + const editorLayout = document.querySelector('.editor-layout'); + + previewToggle.addEventListener('click', function(e) { + e.stopPropagation(); // Prevent event bubbling + previewPanel.classList.toggle('collapsed'); + editorLayout.classList.toggle('preview-collapsed'); + + // Resize Ace Editor after animation + setTimeout(function() { + if (aceEditor) { + aceEditor.resize(); + } + }, 300); + }); + + // Click on collapsed preview to expand + previewPanel.addEventListener('click', function(e) { + if (this.classList.contains('collapsed')) { + this.classList.remove('collapsed'); + editorLayout.classList.remove('preview-collapsed'); + + // Resize Ace Editor after animation + setTimeout(function() { + if (aceEditor) { + aceEditor.resize(); + } + }, 300); + } + }); }); From eca8dca5d23d5527a40696b92eebbc23574707a2 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Sun, 6 Jul 2025 20:57:17 +0200 Subject: [PATCH 03/22] Show folder tree for notes. --- app.py | 381 ++++++++++++- migrate_db.py | 120 +++- models.py | 31 +- templates/note_editor.html | 13 + templates/notes_folders.html | 538 ++++++++++++++++++ templates/notes_list.html | 1025 +++++++++++++++++++++++++++++----- 6 files changed, 1961 insertions(+), 147 deletions(-) create mode 100644 templates/notes_folders.html diff --git a/app.py b/app.py index decaa49..62568c3 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,5 @@ from flask import Flask, render_template, request, redirect, url_for, jsonify, flash, session, g, Response, send_file, abort -from models import db, TimeEntry, WorkConfig, User, SystemSettings, Team, Role, Project, Company, CompanyWorkConfig, CompanySettings, UserPreferences, WorkRegion, AccountType, ProjectCategory, Task, SubTask, TaskStatus, TaskPriority, TaskDependency, Sprint, SprintStatus, Announcement, SystemEvent, WidgetType, UserDashboard, DashboardWidget, WidgetTemplate, Comment, CommentVisibility, BrandingSettings, Note, NoteLink, NoteVisibility +from models import db, TimeEntry, WorkConfig, User, SystemSettings, Team, Role, Project, Company, CompanyWorkConfig, CompanySettings, UserPreferences, WorkRegion, AccountType, ProjectCategory, Task, SubTask, TaskStatus, TaskPriority, TaskDependency, Sprint, SprintStatus, Announcement, SystemEvent, WidgetType, UserDashboard, DashboardWidget, WidgetTemplate, Comment, CommentVisibility, BrandingSettings, Note, NoteLink, NoteVisibility, NoteFolder from data_formatting import ( format_duration, prepare_export_data, prepare_team_hours_export_data, format_table_data, format_graph_data, format_team_data, format_burndown_data @@ -1616,6 +1616,7 @@ def notes_list(): # Get filter parameters visibility_filter = request.args.get('visibility', 'all') tag_filter = request.args.get('tag') + folder_filter = request.args.get('folder') search_query = request.args.get('search', request.args.get('q')) # Base query - all notes in user's company @@ -1654,6 +1655,10 @@ def notes_list(): if tag_filter: query = query.filter(Note.tags.like(f'%{tag_filter}%')) + # Apply folder filter + if folder_filter: + query = query.filter_by(folder=folder_filter) + # Apply search if search_query: query = query.filter( @@ -1672,16 +1677,66 @@ def notes_list(): for note in Note.query.filter_by(company_id=g.user.company_id, is_archived=False).all(): all_tags.update(note.get_tags_list()) + # Get all unique folders for filter dropdown + all_folders = set() + + # Get folders from NoteFolder table + folder_records = NoteFolder.query.filter_by(company_id=g.user.company_id).all() + for folder in folder_records: + all_folders.add(folder.path) + + # Also get folders from notes (for backward compatibility) + folder_notes = Note.query.filter_by(company_id=g.user.company_id, is_archived=False).filter(Note.folder != None).all() + for note in folder_notes: + if note.folder: + all_folders.add(note.folder) + # Get projects for filter projects = Project.query.filter_by(company_id=g.user.company_id, is_active=True).all() + # Build folder tree structure for sidebar + folder_counts = {} + for note in Note.query.filter_by(company_id=g.user.company_id, is_archived=False).all(): + if note.folder and note.can_user_view(g.user): + # Add this folder and all parent folders + parts = note.folder.split('/') + for i in range(len(parts)): + folder_path = '/'.join(parts[:i+1]) + folder_counts[folder_path] = folder_counts.get(folder_path, 0) + (1 if i == len(parts)-1 else 0) + + # Initialize counts for empty folders + for folder_path in all_folders: + if folder_path not in folder_counts: + folder_counts[folder_path] = 0 + + # Build folder tree structure + folder_tree = {} + for folder in sorted(all_folders): + parts = folder.split('/') + current = folder_tree + + for i, part in enumerate(parts): + if i == len(parts) - 1: + # Leaf folder + current[folder] = {} + else: + # Navigate to parent + parent_path = '/'.join(parts[:i+1]) + if parent_path not in current: + current[parent_path] = {} + current = current[parent_path] + return render_template('notes_list.html', title='Notes', notes=notes, visibility_filter=visibility_filter, tag_filter=tag_filter, + folder_filter=folder_filter, search_query=search_query, all_tags=sorted(list(all_tags)), + all_folders=sorted(list(all_folders)), + folder_tree=folder_tree, + folder_counts=folder_counts, projects=projects, NoteVisibility=NoteVisibility) @@ -1695,6 +1750,7 @@ def create_note(): title = request.form.get('title', '').strip() content = request.form.get('content', '').strip() visibility = request.form.get('visibility', 'Private') + folder = request.form.get('folder', '').strip() tags = request.form.get('tags', '').strip() project_id = request.form.get('project_id') task_id = request.form.get('task_id') @@ -1719,6 +1775,7 @@ def create_note(): title=title, content=content, visibility=NoteVisibility[visibility.upper()], # Convert to uppercase for enum access + folder=folder if folder else None, tags=','.join(tag_list) if tag_list else None, created_by_id=g.user.id, company_id=g.user.company_id @@ -1758,11 +1815,26 @@ def create_note(): projects = Project.query.filter_by(company_id=g.user.company_id, is_active=True).all() tasks = [] + # Get all existing folders for suggestions + all_folders = set() + + # Get folders from NoteFolder table + folder_records = NoteFolder.query.filter_by(company_id=g.user.company_id).all() + for folder in folder_records: + all_folders.add(folder.path) + + # Also get folders from notes (for backward compatibility) + folder_notes = Note.query.filter_by(company_id=g.user.company_id, is_archived=False).filter(Note.folder != None).all() + for note in folder_notes: + if note.folder: + all_folders.add(note.folder) + return render_template('note_editor.html', title='New Note', note=None, projects=projects, tasks=tasks, + all_folders=sorted(list(all_folders)), NoteVisibility=NoteVisibility) @@ -1826,6 +1898,7 @@ def edit_note(slug): title = request.form.get('title', '').strip() content = request.form.get('content', '').strip() visibility = request.form.get('visibility', 'Private') + folder = request.form.get('folder', '').strip() tags = request.form.get('tags', '').strip() project_id = request.form.get('project_id') task_id = request.form.get('task_id') @@ -1849,6 +1922,7 @@ def edit_note(slug): note.title = title note.content = content note.visibility = NoteVisibility[visibility.upper()] # Convert to uppercase for enum access + note.folder = folder if folder else None note.tags = ','.join(tag_list) if tag_list else None # Update team_id if visibility is Team @@ -1895,11 +1969,26 @@ def edit_note(slug): if note.project_id: tasks = Task.query.filter_by(project_id=note.project_id).all() + # Get all existing folders for suggestions + all_folders = set() + + # Get folders from NoteFolder table + folder_records = NoteFolder.query.filter_by(company_id=g.user.company_id).all() + for folder in folder_records: + all_folders.add(folder.path) + + # Also get folders from notes (for backward compatibility) + folder_notes = Note.query.filter_by(company_id=g.user.company_id, is_archived=False).filter(Note.folder != None).all() + for n in folder_notes: + if n.folder: + all_folders.add(n.folder) + return render_template('note_editor.html', title=f'Edit: {note.title}', note=note, projects=projects, tasks=tasks, + all_folders=sorted(list(all_folders)), NoteVisibility=NoteVisibility) @@ -1930,6 +2019,296 @@ def delete_note(slug): return redirect(url_for('view_note', slug=slug)) +@app.route('/notes/folders') +@login_required +@company_required +def notes_folders(): + """Manage note folders""" + # Get all folders from NoteFolder table + all_folders = set() + folder_records = NoteFolder.query.filter_by(company_id=g.user.company_id).all() + + for folder in folder_records: + all_folders.add(folder.path) + + # Also get folders from notes (for backward compatibility) + folder_notes = Note.query.filter_by(company_id=g.user.company_id, is_archived=False).filter(Note.folder != None).all() + + folder_counts = {} + for note in folder_notes: + if note.folder and note.can_user_view(g.user): + # Add this folder and all parent folders + parts = note.folder.split('/') + for i in range(len(parts)): + folder_path = '/'.join(parts[:i+1]) + all_folders.add(folder_path) + folder_counts[folder_path] = folder_counts.get(folder_path, 0) + (1 if i == len(parts)-1 else 0) + + # Initialize counts for empty folders + for folder_path in all_folders: + if folder_path not in folder_counts: + folder_counts[folder_path] = 0 + + # Build folder tree structure + folder_tree = {} + for folder in sorted(all_folders): + parts = folder.split('/') + current = folder_tree + + for i, part in enumerate(parts): + if i == len(parts) - 1: + # Leaf folder + current[folder] = {} + else: + # Navigate to parent + parent_path = '/'.join(parts[:i+1]) + if parent_path not in current: + current[parent_path] = {} + current = current[parent_path] + + return render_template('notes_folders.html', + title='Note Folders', + all_folders=sorted(list(all_folders)), + folder_tree=folder_tree, + folder_counts=folder_counts) + + +@app.route('/api/notes/folder-details') +@login_required +@company_required +def api_folder_details(): + """Get details about a specific folder""" + folder_path = request.args.get('path', '') + + if not folder_path: + return jsonify({'error': 'Folder path required'}), 400 + + # Get notes in this folder + notes = Note.query.filter_by( + company_id=g.user.company_id, + folder=folder_path, + is_archived=False + ).all() + + # Filter by visibility + visible_notes = [n for n in notes if n.can_user_view(g.user)] + + # Get subfolders + all_folders = set() + folder_notes = Note.query.filter_by(company_id=g.user.company_id, is_archived=False).filter( + Note.folder.like(f'{folder_path}/%') + ).all() + + for note in folder_notes: + if note.folder and note.can_user_view(g.user): + # Get immediate subfolder + subfolder = note.folder[len(folder_path)+1:] + if '/' in subfolder: + subfolder = subfolder.split('/')[0] + all_folders.add(subfolder) + + # Get recent notes (last 5) + recent_notes = sorted(visible_notes, key=lambda n: n.updated_at, reverse=True)[:5] + + return jsonify({ + 'name': folder_path.split('/')[-1], + 'path': folder_path, + 'note_count': len(visible_notes), + 'subfolder_count': len(all_folders), + 'recent_notes': [ + { + 'title': n.title, + 'slug': n.slug, + 'updated_at': n.updated_at.strftime('%Y-%m-%d %H:%M') + } for n in recent_notes + ] + }) + + +@app.route('/api/notes/folders', methods=['POST']) +@login_required +@company_required +def api_create_folder(): + """Create a new folder""" + data = request.get_json() + folder_name = data.get('name', '').strip() + parent_folder = data.get('parent', '').strip() + + if not folder_name: + return jsonify({'success': False, 'message': 'Folder name is required'}), 400 + + # Validate folder name (no special characters except dash and underscore) + import re + if not re.match(r'^[a-zA-Z0-9_\- ]+$', folder_name): + return jsonify({'success': False, 'message': 'Folder name can only contain letters, numbers, spaces, dashes, and underscores'}), 400 + + # Create full path + full_path = f"{parent_folder}/{folder_name}" if parent_folder else folder_name + + # Check if folder already exists + existing_folder = NoteFolder.query.filter_by( + company_id=g.user.company_id, + path=full_path + ).first() + + if existing_folder: + return jsonify({'success': False, 'message': 'Folder already exists'}), 400 + + # Create the folder + try: + folder = NoteFolder( + name=folder_name, + path=full_path, + parent_path=parent_folder if parent_folder else None, + description=data.get('description', ''), + created_by_id=g.user.id, + company_id=g.user.company_id + ) + + db.session.add(folder) + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Folder created successfully', + 'folder': { + 'name': folder_name, + 'path': full_path + } + }) + except Exception as e: + db.session.rollback() + logger.error(f"Error creating folder: {str(e)}") + return jsonify({'success': False, 'message': 'Error creating folder'}), 500 + + +@app.route('/api/notes/folders', methods=['PUT']) +@login_required +@company_required +def api_rename_folder(): + """Rename an existing folder""" + data = request.get_json() + old_path = data.get('old_path', '').strip() + new_name = data.get('new_name', '').strip() + + if not old_path or not new_name: + return jsonify({'success': False, 'message': 'Old path and new name are required'}), 400 + + # Validate folder name + import re + if not re.match(r'^[a-zA-Z0-9_\- ]+$', new_name): + return jsonify({'success': False, 'message': 'Folder name can only contain letters, numbers, spaces, dashes, and underscores'}), 400 + + # Build new path + path_parts = old_path.split('/') + path_parts[-1] = new_name + new_path = '/'.join(path_parts) + + # Update all notes in this folder and subfolders + notes_to_update = Note.query.filter( + Note.company_id == g.user.company_id, + db.or_( + Note.folder == old_path, + Note.folder.like(f'{old_path}/%') + ) + ).all() + + # Check permissions for all notes + for note in notes_to_update: + if not note.can_user_edit(g.user): + return jsonify({'success': False, 'message': 'You do not have permission to modify all notes in this folder'}), 403 + + # Update folder paths + try: + for note in notes_to_update: + if note.folder == old_path: + note.folder = new_path + else: + # Update subfolder path + note.folder = new_path + note.folder[len(old_path):] + + db.session.commit() + + return jsonify({ + 'success': True, + 'message': f'Renamed folder to {new_name}', + 'updated_count': len(notes_to_update) + }) + except Exception as e: + db.session.rollback() + logger.error(f"Error renaming folder: {str(e)}") + return jsonify({'success': False, 'message': 'Error renaming folder'}), 500 + + +@app.route('/api/notes/folders', methods=['DELETE']) +@login_required +@company_required +def api_delete_folder(): + """Delete an empty folder""" + folder_path = request.args.get('path', '').strip() + + if not folder_path: + return jsonify({'success': False, 'message': 'Folder path is required'}), 400 + + # Check if folder has any notes + notes_in_folder = Note.query.filter_by( + company_id=g.user.company_id, + folder=folder_path, + is_archived=False + ).all() + + if notes_in_folder: + return jsonify({'success': False, 'message': 'Cannot delete folder that contains notes'}), 400 + + # Check if folder has subfolders with notes + notes_in_subfolders = Note.query.filter( + Note.company_id == g.user.company_id, + Note.folder.like(f'{folder_path}/%'), + Note.is_archived == False + ).first() + + if notes_in_subfolders: + return jsonify({'success': False, 'message': 'Cannot delete folder that contains subfolders with notes'}), 400 + + # Since we don't have a separate folders table, we just return success + # The folder will disappear from the UI when there are no notes in it + + return jsonify({ + 'success': True, + 'message': 'Folder deleted successfully' + }) + + +@app.route('/api/notes//folder', methods=['PUT']) +@login_required +@company_required +def update_note_folder(slug): + """Update a note's folder via drag and drop""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_edit(g.user): + return jsonify({'success': False, 'message': 'Permission denied'}), 403 + + data = request.get_json() + folder_path = data.get('folder', '').strip() + + try: + # Update the note's folder + note.folder = folder_path if folder_path else None + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Note moved successfully', + 'folder': folder_path + }) + except Exception as e: + db.session.rollback() + logger.error(f"Error updating note folder: {str(e)}") + return jsonify({'success': False, 'message': 'Error updating note folder'}), 500 + + @app.route('/api/notes//link', methods=['POST']) @login_required @company_required diff --git a/migrate_db.py b/migrate_db.py index bc0728c..d80a97a 100644 --- a/migrate_db.py +++ b/migrate_db.py @@ -1280,7 +1280,21 @@ def migrate_postgresql_schema(): WHERE table_name = 'note' """)) - if not result.fetchone(): + if result.fetchone(): + # Table exists, check for folder column + result = db.session.execute(text(""" + SELECT column_name + FROM information_schema.columns + WHERE table_name = 'note' AND column_name = 'folder' + """)) + + if not result.fetchone(): + print("Adding folder column to note table...") + db.session.execute(text("ALTER TABLE note ADD COLUMN folder VARCHAR(100)")) + db.session.execute(text("CREATE INDEX IF NOT EXISTS idx_note_folder ON note(folder)")) + db.session.commit() + print("Folder column added successfully!") + else: print("Creating note and note_link tables...") # Create NoteVisibility enum type @@ -1299,6 +1313,7 @@ def migrate_postgresql_schema(): content TEXT NOT NULL, slug VARCHAR(100) NOT NULL, visibility notevisibility NOT NULL DEFAULT 'Private', + folder VARCHAR(100), company_id INTEGER NOT NULL, created_by_id INTEGER NOT NULL, project_id INTEGER, @@ -1327,6 +1342,31 @@ def migrate_postgresql_schema(): ) """)) + # Check if note_folder table exists + result = db.session.execute(text(""" + SELECT table_name + FROM information_schema.tables + WHERE table_name = 'note_folder' + """)) + + if not result.fetchone(): + print("Creating note_folder table...") + db.session.execute(text(""" + CREATE TABLE note_folder ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + path VARCHAR(500) NOT NULL, + parent_path VARCHAR(500), + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by_id INTEGER NOT NULL, + company_id INTEGER NOT NULL, + FOREIGN KEY (created_by_id) REFERENCES "user" (id), + FOREIGN KEY (company_id) REFERENCES company (id), + CONSTRAINT uq_folder_path_company UNIQUE (path, company_id) + ) + """)) + # Create indexes db.session.execute(text("CREATE INDEX idx_note_company ON note(company_id)")) db.session.execute(text("CREATE INDEX idx_note_created_by ON note(created_by_id)")) @@ -1334,11 +1374,23 @@ def migrate_postgresql_schema(): db.session.execute(text("CREATE INDEX idx_note_task ON note(task_id)")) db.session.execute(text("CREATE INDEX idx_note_slug ON note(company_id, slug)")) db.session.execute(text("CREATE INDEX idx_note_visibility ON note(visibility)")) - db.session.execute(text("CREATE INDEX idx_note_archived ON note(archived)")) + db.session.execute(text("CREATE INDEX idx_note_archived ON note(is_archived)")) db.session.execute(text("CREATE INDEX idx_note_created_at ON note(created_at DESC)")) + db.session.execute(text("CREATE INDEX idx_note_folder ON note(folder)")) db.session.execute(text("CREATE INDEX idx_note_link_source ON note_link(source_note_id)")) db.session.execute(text("CREATE INDEX idx_note_link_target ON note_link(target_note_id)")) + # Create indexes for note_folder if table was created + result = db.session.execute(text(""" + SELECT table_name + FROM information_schema.tables + WHERE table_name = 'note_folder' + """)) + if result.fetchone(): + db.session.execute(text("CREATE INDEX IF NOT EXISTS idx_note_folder_company ON note_folder(company_id)")) + db.session.execute(text("CREATE INDEX IF NOT EXISTS idx_note_folder_parent_path ON note_folder(parent_path)")) + db.session.execute(text("CREATE INDEX IF NOT EXISTS idx_note_folder_created_by ON note_folder(created_by_id)")) + db.session.commit() print("PostgreSQL schema migration completed successfully!") @@ -1568,7 +1620,46 @@ def migrate_notes_system(db_file=None): # Check if note table already exists cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='note'") if cursor.fetchone(): - print("Note table already exists. Skipping migration.") + print("Note table already exists. Checking for updates...") + + # Check if folder column exists + cursor.execute("PRAGMA table_info(note)") + columns = [column[1] for column in cursor.fetchall()] + + if 'folder' not in columns: + print("Adding folder column to note table...") + cursor.execute("ALTER TABLE note ADD COLUMN folder VARCHAR(100)") + cursor.execute("CREATE INDEX IF NOT EXISTS idx_note_folder ON note(folder)") + conn.commit() + print("Folder column added successfully!") + + # Check if note_folder table exists + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='note_folder'") + if not cursor.fetchone(): + print("Creating note_folder table...") + cursor.execute(""" + CREATE TABLE note_folder ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(100) NOT NULL, + path VARCHAR(500) NOT NULL, + parent_path VARCHAR(500), + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by_id INTEGER NOT NULL, + company_id INTEGER NOT NULL, + FOREIGN KEY (created_by_id) REFERENCES user(id), + FOREIGN KEY (company_id) REFERENCES company(id), + UNIQUE(path, company_id) + ) + """) + + # Create indexes for note_folder + cursor.execute("CREATE INDEX idx_note_folder_company ON note_folder(company_id)") + cursor.execute("CREATE INDEX idx_note_folder_parent_path ON note_folder(parent_path)") + cursor.execute("CREATE INDEX idx_note_folder_created_by ON note_folder(created_by_id)") + conn.commit() + print("Note folder table created successfully!") + return True print("Creating Notes system tables...") @@ -1581,6 +1672,7 @@ def migrate_notes_system(db_file=None): content TEXT NOT NULL, slug VARCHAR(100) NOT NULL, visibility VARCHAR(20) NOT NULL DEFAULT 'Private', + folder VARCHAR(100), company_id INTEGER NOT NULL, created_by_id INTEGER NOT NULL, project_id INTEGER, @@ -1625,6 +1717,28 @@ def migrate_notes_system(db_file=None): # Create indexes for note links cursor.execute("CREATE INDEX idx_note_link_source ON note_link(source_note_id)") cursor.execute("CREATE INDEX idx_note_link_target ON note_link(target_note_id)") + + # Create note_folder table + cursor.execute(""" + CREATE TABLE note_folder ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(100) NOT NULL, + path VARCHAR(500) NOT NULL, + parent_path VARCHAR(500), + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by_id INTEGER NOT NULL, + company_id INTEGER NOT NULL, + FOREIGN KEY (created_by_id) REFERENCES user(id), + FOREIGN KEY (company_id) REFERENCES company(id), + UNIQUE(path, company_id) + ) + """) + + # Create indexes for note_folder + cursor.execute("CREATE INDEX idx_note_folder_company ON note_folder(company_id)") + cursor.execute("CREATE INDEX idx_note_folder_parent_path ON note_folder(parent_path)") + cursor.execute("CREATE INDEX idx_note_folder_created_by ON note_folder(created_by_id)") conn.commit() print("Notes system migration completed successfully!") diff --git a/models.py b/models.py index 1c87c0c..61c232f 100644 --- a/models.py +++ b/models.py @@ -1261,6 +1261,9 @@ class Note(db.Model): # Visibility and sharing visibility = db.Column(db.Enum(NoteVisibility), nullable=False, default=NoteVisibility.PRIVATE) + # Folder organization + folder = db.Column(db.String(100), nullable=True) # Folder path like "Work/Projects" or "Personal" + # Metadata created_at = db.Column(db.DateTime, default=datetime.now) updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) @@ -1417,4 +1420,30 @@ class NoteLink(db.Model): __table_args__ = (db.UniqueConstraint('source_note_id', 'target_note_id', name='uq_note_link'),) def __repr__(self): - return f' {self.target_note_id}>' \ No newline at end of file + return f' {self.target_note_id}>' + + +class NoteFolder(db.Model): + """Represents a folder for organizing notes""" + id = db.Column(db.Integer, primary_key=True) + + # Folder properties + name = db.Column(db.String(100), nullable=False) + path = db.Column(db.String(500), nullable=False) # Full path like "Work/Projects/Q1" + parent_path = db.Column(db.String(500), nullable=True) # Parent folder path + description = db.Column(db.Text, nullable=True) + + # Metadata + created_at = db.Column(db.DateTime, default=datetime.now) + created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + company_id = db.Column(db.Integer, db.ForeignKey('company.id'), nullable=False) + + # Relationships + created_by = db.relationship('User', foreign_keys=[created_by_id]) + company = db.relationship('Company', foreign_keys=[company_id]) + + # Unique constraint to prevent duplicate paths within a company + __table_args__ = (db.UniqueConstraint('path', 'company_id', name='uq_folder_path_company'),) + + def __repr__(self): + return f'' \ No newline at end of file diff --git a/templates/note_editor.html b/templates/note_editor.html index 623cff8..05f7555 100644 --- a/templates/note_editor.html +++ b/templates/note_editor.html @@ -59,6 +59,19 @@

    Note Settings

    +
    + + + + {% for folder in all_folders %} + +
    +
    +
    +

    Note Folders

    +
    + + Back to Notes +
    +
    + +
    + +
    +

    Folder Structure

    +
    + {{ render_folder_tree(folder_tree)|safe }} +
    +
    + + +
    +
    +

    Select a folder to view details

    +
    +
    +
    +
    + + + + + + + + +{% endblock %} + +{% macro render_folder_tree(tree, level=0) %} + {% for folder, children in tree.items() %} +
    +
    + {% if children %} + + {% endif %} + 📁 + {{ folder.split('/')[-1] }} + ({{ folder_counts.get(folder, 0) }}) +
    + {% if children %} +
    + {{ render_folder_tree(children, level + 1)|safe }} +
    + {% endif %} +
    + {% endfor %} +{% endmacro %} \ No newline at end of file diff --git a/templates/notes_list.html b/templates/notes_list.html index cc56834..3e6f4b6 100644 --- a/templates/notes_list.html +++ b/templates/notes_list.html @@ -5,14 +5,60 @@

    Notes

    + + + + ⚙️ Manage Folders + Create New Note
    + +
    + +
    + +
    +
    +
    + 🏠 + All Notes + ({{ notes|length }}) +
    +
    + {{ render_folder_tree(folder_tree)|safe }} +
    +
    + + +
    +
    + + +
    -
    - - -
    Notes
    {% if notes %} -
    - {% for note in notes %} -
    -
    -

    - {{ note.title }} -

    -
    - - {% if note.visibility.value == 'Private' %}🔒{% elif note.visibility.value == 'Team' %}👥{% else %}🏢{% endif %} - {{ note.visibility.value }} - - {% if note.updated_at > note.created_at %} - Updated {{ note.updated_at|format_date }} - {% else %} - Created {{ note.created_at|format_date }} - {% endif %} + +
    + + + + + + + + + + + + + + {% for note in notes %} + + + + + + + + + + {% endfor %} + +
    TitleFolderVisibilityTagsUpdatedActions
    + {% if note.is_pinned %} + 📌 + {% endif %} + + + {{ note.title }} + +
    + {% if note.project %} + + 📁 {{ note.project.code }} + + {% endif %} + {% if note.task %} + + ✓ #{{ note.task.id }} + + {% endif %} +
    +
    + {% if note.folder %} + {{ note.folder }} + {% else %} + Root + {% endif %} + + + {% if note.visibility.value == 'Private' %}🔒{% elif note.visibility.value == 'Team' %}👥{% else %}🏢{% endif %} + {{ note.visibility.value }} + + + {% if note.tags %} + {% for tag in note.get_tags_list() %} + {{ tag }} + {% endfor %} + {% endif %} + + {{ note.updated_at|format_date }} + +
    + View + {% if note.can_user_edit(g.user) %} + Edit + + + + {% endif %} +
    +
    +
    + + + +
    +
    -{% endblock %} \ No newline at end of file + + +{% endblock %} + +{% macro render_folder_tree(tree, level=0) %} + {% for folder, children in tree.items() %} +
    +
    + {% if children %} + + {% endif %} + 📁 + {{ folder.split('/')[-1] }} + ({{ folder_counts.get(folder, 0) }}) +
    + {% if children %} +
    + {{ render_folder_tree(children, level + 1)|safe }} +
    + {% endif %} +
    + {% endfor %} +{% endmacro %} \ No newline at end of file From be370708a70d208fc899fa17f9832c6e8205028c Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Sun, 6 Jul 2025 21:18:19 +0200 Subject: [PATCH 04/22] Add tags tree for notes. --- app.py | 59 ++- migrations/add_folder_to_notes.sql | 5 + migrations/add_note_folder_table.sql | 17 + templates/note_editor.html | 2 +- templates/notes_list.html | 555 ++++++++++++++++++++++++--- 5 files changed, 571 insertions(+), 67 deletions(-) create mode 100644 migrations/add_folder_to_notes.sql create mode 100644 migrations/add_note_folder_table.sql diff --git a/app.py b/app.py index 62568c3..d5d6013 100644 --- a/app.py +++ b/app.py @@ -1672,10 +1672,21 @@ def notes_list(): # Order by pinned first, then by updated date notes = query.order_by(Note.is_pinned.desc(), Note.updated_at.desc()).all() - # Get all unique tags for filter dropdown + # Get all unique tags for filter dropdown and count them all_tags = set() + tag_counts = {} + visibility_counts = {'private': 0, 'team': 0, 'company': 0} + for note in Note.query.filter_by(company_id=g.user.company_id, is_archived=False).all(): - all_tags.update(note.get_tags_list()) + if note.can_user_view(g.user): + # Count tags + note_tags = note.get_tags_list() + all_tags.update(note_tags) + for tag in note_tags: + tag_counts[tag] = tag_counts.get(tag, 0) + 1 + + # Count visibility + visibility_counts[note.visibility.value.lower()] = visibility_counts.get(note.visibility.value.lower(), 0) + 1 # Get all unique folders for filter dropdown all_folders = set() @@ -1737,6 +1748,8 @@ def notes_list(): all_folders=sorted(list(all_folders)), folder_tree=folder_tree, folder_counts=folder_counts, + tag_counts=tag_counts, + visibility_counts=visibility_counts, projects=projects, NoteVisibility=NoteVisibility) @@ -2309,6 +2322,48 @@ def update_note_folder(slug): return jsonify({'success': False, 'message': 'Error updating note folder'}), 500 +@app.route('/api/notes//tags', methods=['POST']) +@login_required +@company_required +def add_tags_to_note(note_id): + """Add tags to a note""" + note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_edit(g.user): + return jsonify({'success': False, 'message': 'Permission denied'}), 403 + + data = request.get_json() + new_tags = data.get('tags', '').strip() + + if not new_tags: + return jsonify({'success': False, 'message': 'No tags provided'}), 400 + + try: + # Get existing tags + existing_tags = note.get_tags_list() + + # Parse new tags + new_tag_list = [tag.strip() for tag in new_tags.split(',') if tag.strip()] + + # Merge tags (avoid duplicates) + all_tags = list(set(existing_tags + new_tag_list)) + + # Update note + note.set_tags_list(all_tags) + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Tags added successfully', + 'tags': note.tags + }) + except Exception as e: + db.session.rollback() + logger.error(f"Error adding tags to note: {str(e)}") + return jsonify({'success': False, 'message': 'Error adding tags'}), 500 + + @app.route('/api/notes//link', methods=['POST']) @login_required @company_required diff --git a/migrations/add_folder_to_notes.sql b/migrations/add_folder_to_notes.sql new file mode 100644 index 0000000..e7bf3fb --- /dev/null +++ b/migrations/add_folder_to_notes.sql @@ -0,0 +1,5 @@ +-- Add folder column to notes table +ALTER TABLE note ADD COLUMN IF NOT EXISTS folder VARCHAR(100); + +-- Create an index on folder for faster filtering +CREATE INDEX IF NOT EXISTS idx_note_folder ON note(folder) WHERE folder IS NOT NULL; \ No newline at end of file diff --git a/migrations/add_note_folder_table.sql b/migrations/add_note_folder_table.sql new file mode 100644 index 0000000..4b1687a --- /dev/null +++ b/migrations/add_note_folder_table.sql @@ -0,0 +1,17 @@ +-- Create note_folder table for tracking folders independently of notes +CREATE TABLE IF NOT EXISTS note_folder ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + path VARCHAR(500) NOT NULL, + parent_path VARCHAR(500), + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by_id INTEGER NOT NULL REFERENCES "user"(id), + company_id INTEGER NOT NULL REFERENCES company(id), + CONSTRAINT uq_folder_path_company UNIQUE (path, company_id) +); + +-- Create indexes for better performance +CREATE INDEX IF NOT EXISTS idx_note_folder_company ON note_folder(company_id); +CREATE INDEX IF NOT EXISTS idx_note_folder_parent_path ON note_folder(parent_path); +CREATE INDEX IF NOT EXISTS idx_note_folder_created_by ON note_folder(created_by_id); \ No newline at end of file diff --git a/templates/note_editor.html b/templates/note_editor.html index 05f7555..b3ded1c 100644 --- a/templates/note_editor.html +++ b/templates/note_editor.html @@ -76,7 +76,7 @@

    Note Settings

    + value="{{ note.tags if note and note.tags else '' }}">
    diff --git a/templates/notes_list.html b/templates/notes_list.html index 3e6f4b6..bebba42 100644 --- a/templates/notes_list.html +++ b/templates/notes_list.html @@ -31,7 +31,7 @@

    Folders

    -
    +
    🏠 All Notes ({{ notes|length }}) @@ -39,6 +39,73 @@

    Folders

    {{ render_folder_tree(folder_tree)|safe }}
    + + +
    +
    +
    + +

    Tags

    + ({{ all_tags|length }}) +
    + +
    +
    + {% if tag_filter %} +
    + ✖️ + Clear filter +
    + {% endif %} + {% if all_tags %} + {% for tag in all_tags %} +
    + 🏷️ + {{ tag }} + ({{ tag_counts.get(tag, 0) }}) +
    + {% endfor %} + {% else %} +
    No tags yet
    + {% endif %} +
    +
    + + +
    +
    +
    + +

    Visibility

    +
    +
    +
    +
    + 👁️ + All Notes + ({{ notes|length }}) +
    +
    + 🔒 + Private + ({{ visibility_counts.get('private', 0) }}) +
    +
    + 👥 + Team + ({{ visibility_counts.get('team', 0) }}) +
    +
    + 🏢 + Company + ({{ visibility_counts.get('company', 0) }}) +
    +
    +
    @@ -46,43 +113,41 @@

    Folders

    -
    + + + + + +
    -
    - - -
    -
    - - -
    -
    - - -
    - -
    + + {% if folder_filter or tag_filter or visibility_filter %} +
    + Active filters: + {% if folder_filter %} + + 📁 {{ folder_filter }} × + + {% endif %} + {% if tag_filter %} + + 🏷️ {{ tag_filter }} × + + {% endif %} + {% if visibility_filter %} + + {% if visibility_filter == 'private' %}🔒{% elif visibility_filter == 'team' %}👥{% else %}🏢{% endif %} + {{ visibility_filter|title }} × + + {% endif %} + +
    + {% endif %}
    @@ -349,6 +414,11 @@

    background: #f8f9fa; } +.folder-content.active { + background: #e3f2fd; + font-weight: 500; +} + .folder-content.drag-over { background: #e3f2fd; border: 2px dashed #2196F3; @@ -382,6 +452,240 @@

    display: block; } +/* Tags section styles */ +.tags-section { + margin-top: 2rem; + border-top: 1px solid #dee2e6; + padding-top: 1rem; +} + +.section-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.section-title { + display: flex; + align-items: center; + cursor: pointer; + padding: 0.5rem; + margin: -0.5rem; + border-radius: 4px; + transition: background 0.2s; + flex: 1; +} + +.section-title:hover { + background: #f8f9fa; +} + +.section-title h3 { + margin: 0; + font-size: 1.1rem; + color: #333; + flex: 1; + margin-left: 0.5rem; +} + +.section-toggle { + font-size: 0.8rem; + transition: transform 0.2s; +} + +.section-toggle.collapsed { + transform: rotate(-90deg); +} + +.tags-count { + font-size: 0.85rem; + color: #666; +} + +.tags-list { + margin-top: 0.5rem; + transition: max-height 0.3s ease-out; + overflow: hidden; +} + +.tags-list.collapsed { + max-height: 0; +} + +.tag-item { + display: flex; + align-items: center; + padding: 0.4rem 0.75rem; + margin: 0.25rem 0; + border-radius: 4px; + cursor: pointer; + transition: background 0.2s; +} + +.tag-item:hover { + background: #f8f9fa; +} + +.tag-item.active { + background: #e3f2fd; + font-weight: 500; +} + +.tag-item.clear-filter { + color: #dc3545; + font-size: 0.85rem; +} + +.tag-icon { + margin-right: 0.5rem; + font-size: 0.9rem; +} + +.tag-name { + flex: 1; + font-size: 0.9rem; +} + +.tag-count { + font-size: 0.85rem; + color: #666; + margin-left: 0.5rem; +} + +.no-tags { + padding: 1rem; + text-align: center; + color: #999; + font-size: 0.9rem; +} + +/* Visibility section styles */ +.visibility-section { + margin-top: 2rem; + border-top: 1px solid #dee2e6; + padding-top: 1rem; +} + +.visibility-list { + margin-top: 0.5rem; + transition: max-height 0.3s ease-out; + overflow: hidden; +} + +.visibility-list.collapsed { + max-height: 0; +} + +.visibility-item { + display: flex; + align-items: center; + padding: 0.4rem 0.75rem; + margin: 0.25rem 0; + border-radius: 4px; + cursor: pointer; + transition: background 0.2s; +} + +.visibility-item:hover { + background: #f8f9fa; +} + +.visibility-item.active { + background: #e3f2fd; + font-weight: 500; +} + +.visibility-icon { + margin-right: 0.5rem; + font-size: 0.9rem; +} + +.visibility-name { + flex: 1; + font-size: 0.9rem; +} + +.visibility-count { + font-size: 0.85rem; + color: #666; + margin-left: 0.5rem; +} + +/* Simplified filter section */ +.notes-filter-section { + background: #f8f9fa; + padding: 1rem; + margin-bottom: 1.5rem; + border-radius: 8px; + border: 1px solid #dee2e6; +} + +.filter-row { + display: flex; + gap: 1rem; + align-items: center; + flex-wrap: wrap; +} + +.search-group { + flex: 1; + display: flex; + gap: 0.5rem; + min-width: 300px; +} + +.search-input { + flex: 1; + padding: 0.5rem 1rem; + border: 2px solid #e9ecef; + border-radius: 6px; + font-size: 0.95rem; +} + +.search-input:focus { + outline: none; + border-color: var(--primary-color); +} + +.active-filters { + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; +} + +.filter-label { + font-size: 0.85rem; + color: #666; +} + +.filter-badge { + background: #e3f2fd; + color: #333; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.85rem; + cursor: pointer; + transition: background 0.2s; + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.filter-badge:hover { + background: #bbdefb; +} + +.filter-badge .remove { + font-weight: bold; + color: #666; +} + +.btn-xs { + padding: 0.2rem 0.5rem; + font-size: 0.75rem; +} + /* Drag and drop styles */ .note-row.dragging, .note-card.dragging { @@ -441,37 +745,7 @@

    align-items: flex-end; } -.filter-group { - flex: 1; - min-width: 200px; -} - -.filter-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: 500; - color: #333; -} - -.filter-group select, -.filter-group input { - width: 100%; - padding: 0.5rem; - border: 2px solid #e9ecef; - border-radius: 6px; - font-size: 0.9rem; -} - -.search-group { - flex: 2; - display: flex; - gap: 0.5rem; - align-items: flex-end; -} -.search-input { - flex: 1; -} /* Table View Styles */ .notes-table { @@ -847,6 +1121,13 @@

    border-radius: 4px; font-size: 0.95rem; } + +.form-text { + display: block; + margin-top: 0.25rem; + font-size: 0.85rem; + color: #6c757d; +} {% endblock %} @@ -1094,7 +1521,7 @@

    Create New Folder

    {% macro render_folder_tree(tree, level=0) %} {% for folder, children in tree.items() %}
    -
    +
    {% if children %} {% endif %} From cc9eaddc1d99f97a2012287effdd267aa7f66d45 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Sun, 6 Jul 2025 21:25:06 +0200 Subject: [PATCH 05/22] Improve notes search bar. --- templates/notes_list.html | 156 ++++++++++++++++++++++++-------------- 1 file changed, 99 insertions(+), 57 deletions(-) diff --git a/templates/notes_list.html b/templates/notes_list.html index bebba42..0110e2e 100644 --- a/templates/notes_list.html +++ b/templates/notes_list.html @@ -120,34 +120,38 @@

    Visibility

    -
    - - -
    - - {% if folder_filter or tag_filter or visibility_filter %} -
    - Active filters: - {% if folder_filter %} - - 📁 {{ folder_filter }} × - - {% endif %} - {% if tag_filter %} - - 🏷️ {{ tag_filter }} × - - {% endif %} - {% if visibility_filter %} - - {% if visibility_filter == 'private' %}🔒{% elif visibility_filter == 'team' %}👥{% else %}🏢{% endif %} - {{ visibility_filter|title }} × - - {% endif %} - +
    +
    - {% endif %}
    @@ -627,58 +631,87 @@

    flex-wrap: wrap; } -.search-group { +.search-container { flex: 1; - display: flex; - gap: 0.5rem; - min-width: 300px; + min-width: 400px; } -.search-input { - flex: 1; - padding: 0.5rem 1rem; +.search-bar { + position: relative; + display: flex; + align-items: center; + background: white; border: 2px solid #e9ecef; - border-radius: 6px; - font-size: 0.95rem; + border-radius: 8px; + padding: 0.25rem; + transition: border-color 0.2s; } -.search-input:focus { - outline: none; +.search-bar:focus-within { border-color: var(--primary-color); } -.active-filters { +.active-filters-inline { display: flex; - align-items: center; gap: 0.5rem; - flex-wrap: wrap; -} - -.filter-label { - font-size: 0.85rem; - color: #666; + padding-left: 0.5rem; + flex-shrink: 0; } -.filter-badge { +.filter-chip { background: #e3f2fd; color: #333; - padding: 0.25rem 0.75rem; - border-radius: 20px; - font-size: 0.85rem; + padding: 0.2rem 0.6rem; + border-radius: 16px; + font-size: 0.8rem; cursor: pointer; transition: background 0.2s; display: inline-flex; align-items: center; - gap: 0.5rem; + gap: 0.4rem; + white-space: nowrap; } -.filter-badge:hover { +.filter-chip:hover { background: #bbdefb; } -.filter-badge .remove { +.filter-chip .remove { font-weight: bold; color: #666; + font-size: 0.9rem; +} + +.search-input { + flex: 1; + padding: 0.4rem 0.75rem; + border: none; + background: transparent; + font-size: 0.95rem; + outline: none; +} + +.btn-search { + padding: 0.4rem; + background: transparent; + color: #666; + border: none; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + flex-shrink: 0; +} + +.btn-search:hover { + background: #e9ecef; + color: var(--primary-color, #007bff); +} + +.btn-search svg { + display: block; } .btn-xs { @@ -1016,13 +1049,18 @@

    flex-direction: column; } - .filter-group { + .search-container { min-width: 100%; } - .search-group { - flex-direction: column; - align-items: stretch; + .search-bar { + flex-wrap: wrap; + padding: 0.5rem; + } + + .active-filters-inline { + width: 100%; + padding: 0 0 0.5rem 0; } .notes-grid { @@ -1037,6 +1075,10 @@

    .notes-table { font-size: 0.85rem; } + + .folder-sidebar { + width: 200px; + } } /* Modal styles */ From d28c7bc83ebc42641c14d7c17c1922b5c9d833de Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Sun, 6 Jul 2025 21:41:30 +0200 Subject: [PATCH 06/22] Improve layout sizes for Table and Grid View. --- templates/notes_list.html | 294 ++++++++++++++++++++++++++++++++------ 1 file changed, 251 insertions(+), 43 deletions(-) diff --git a/templates/notes_list.html b/templates/notes_list.html index 0110e2e..dc09961 100644 --- a/templates/notes_list.html +++ b/templates/notes_list.html @@ -8,19 +8,23 @@

    Notes

    - ⚙️ Manage Folders - Create New Note

    + {% if notes %} + +
    +
    + {{ notes|length }} notes +
    +
    + +
    +
    +
    @@ -221,12 +243,25 @@

    Visibility

    @@ -243,12 +243,12 @@

    Visibility

    @@ -112,7 +112,7 @@

    No Users Found

    diff --git a/templates/config.html b/templates/config.html index 21fa8ec..09bbe8b 100644 --- a/templates/config.html +++ b/templates/config.html @@ -192,33 +192,33 @@

    Example:

    if (interval === 15) { if (roundToNearest) { - examples.push('9:07 AM → 9:00 AM'); - examples.push('9:08 AM → 9:15 AM'); - examples.push('9:23 AM → 9:30 AM'); + examples.push('9:07 AM 9:00 AM'); + examples.push('9:08 AM 9:15 AM'); + examples.push('9:23 AM 9:30 AM'); } else { - examples.push('9:01 AM → 9:15 AM'); - examples.push('9:16 AM → 9:30 AM'); - examples.push('9:31 AM → 9:45 AM'); + examples.push('9:01 AM 9:15 AM'); + examples.push('9:16 AM 9:30 AM'); + examples.push('9:31 AM 9:45 AM'); } } else if (interval === 30) { if (roundToNearest) { - examples.push('9:14 AM → 9:00 AM'); - examples.push('9:16 AM → 9:30 AM'); - examples.push('9:45 AM → 10:00 AM'); + examples.push('9:14 AM 9:00 AM'); + examples.push('9:16 AM 9:30 AM'); + examples.push('9:45 AM 10:00 AM'); } else { - examples.push('9:01 AM → 9:30 AM'); - examples.push('9:31 AM → 10:00 AM'); - examples.push('10:01 AM → 10:30 AM'); + examples.push('9:01 AM 9:30 AM'); + examples.push('9:31 AM 10:00 AM'); + examples.push('10:01 AM 10:30 AM'); } } else if (interval === 60) { if (roundToNearest) { - examples.push('9:29 AM → 9:00 AM'); - examples.push('9:31 AM → 10:00 AM'); - examples.push('10:30 AM → 11:00 AM'); + examples.push('9:29 AM 9:00 AM'); + examples.push('9:31 AM 10:00 AM'); + examples.push('10:30 AM 11:00 AM'); } else { - examples.push('9:01 AM → 10:00 AM'); - examples.push('10:01 AM → 11:00 AM'); - examples.push('11:01 AM → 12:00 PM'); + examples.push('9:01 AM 10:00 AM'); + examples.push('10:01 AM 11:00 AM'); + examples.push('11:01 AM 12:00 PM'); } } diff --git a/templates/confirm_company_deletion.html b/templates/confirm_company_deletion.html index 4f3e7c5..dba1bf1 100644 --- a/templates/confirm_company_deletion.html +++ b/templates/confirm_company_deletion.html @@ -3,10 +3,10 @@ {% block content %}
    -

    ⚠️ Confirm Company Deletion

    +

    Confirm Company Deletion

    Critical Action Required - Review All Data Before Proceeding

    ← Back to User Management + class="btn btn-md btn-secondary"> Back to User Management
    @@ -21,7 +21,7 @@

    The following data will be permanently deleted:

    -

    🏢 Company Information

    +

    Company Information

    - View + + + + + {% if note.can_user_edit(g.user) %} - Edit + + + + +
    - +
    {% endif %}
    @@ -288,12 +323,25 @@

    - View + + + + + {% if note.can_user_edit(g.user) %} - Edit + + + + +
    - +
    {% endif %}
    @@ -338,6 +386,35 @@

    flex-shrink: 0; } +.create-note-section { + margin-bottom: 1.5rem; +} + +.btn-create-note { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + width: 100%; + padding: 0.75rem 1rem; + background: var(--primary-color, #007bff); + color: white; + text-decoration: none; + border-radius: 6px; + font-weight: 500; + transition: background 0.2s; +} + +.btn-create-note:hover { + background: var(--primary-dark, #0056b3); + color: white; + text-decoration: none; +} + +.btn-create-note svg { + flex-shrink: 0; +} + .sidebar-header { display: flex; justify-content: space-between; @@ -719,6 +796,62 @@

    font-size: 0.75rem; } +/* Notes toolbar */ +.notes-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 0; + border-bottom: 1px solid #dee2e6; + margin-bottom: 1rem; +} + +.toolbar-left { + display: flex; + align-items: center; + gap: 1rem; +} + +.toolbar-right { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.notes-count { + font-size: 0.9rem; + color: #666; + font-weight: 500; +} + +.btn-view-toggle { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.8rem; + background: white; + border: 1px solid #dee2e6; + border-radius: 6px; + color: #333; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.2s; +} + +.btn-view-toggle:hover { + background: #f8f9fa; + border-color: #adb5bd; +} + +.btn-view-toggle svg { + width: 16px; + height: 16px; +} + +.view-label { + font-weight: 500; +} + /* Drag and drop styles */ .note-row.dragging, .note-card.dragging { @@ -749,15 +882,6 @@

    align-items: center; } -#toggle-view { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.view-icon { - font-size: 1.2rem; -} .notes-filter-section { background: #f8f9fa; @@ -882,7 +1006,41 @@

    } .column-actions { - width: 180px; + width: 120px; +} + +.note-actions { + display: flex; + gap: 0.5rem; + justify-content: center; +} + +.btn-action { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + background: transparent; + border: 1px solid #dee2e6; + border-radius: 4px; + color: #495057; + transition: all 0.2s; + cursor: pointer; +} + +.btn-action:hover { + background: #f8f9fa; + border-color: #adb5bd; + color: var(--primary-color, #007bff); + text-decoration: none; +} + +.btn-action-danger:hover { + background: #f8d7da; + border-color: #f5c6cb; + color: #721c24; } .note-associations { @@ -961,16 +1119,16 @@

    /* Grid View Styles */ .notes-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); - gap: 1.5rem; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1rem; margin-bottom: 2rem; } .note-card { background: white; border: 1px solid #dee2e6; - border-radius: 8px; - padding: 1.5rem; + border-radius: 6px; + padding: 1rem; transition: box-shadow 0.2s ease; display: flex; flex-direction: column; @@ -981,15 +1139,15 @@

    } .note-header { - margin-bottom: 1rem; + margin-bottom: 0.75rem; } .note-title { - margin: 0 0 0.5rem 0; - font-size: 1.25rem; + margin: 0 0 0.4rem 0; + font-size: 1.1rem; display: flex; align-items: center; - gap: 0.5rem; + gap: 0.4rem; } .note-title a { @@ -1003,21 +1161,22 @@

    .note-meta { display: flex; - gap: 0.75rem; + gap: 0.5rem; align-items: center; - font-size: 0.85rem; + font-size: 0.75rem; color: #666; flex-wrap: wrap; } .note-preview { flex: 1; - margin-bottom: 1rem; + margin-bottom: 0.75rem; color: #555; - line-height: 1.6; + line-height: 1.4; + font-size: 0.85rem; overflow: hidden; display: -webkit-box; - -webkit-line-clamp: 3; + -webkit-line-clamp: 2; -webkit-box-orient: vertical; } @@ -1025,17 +1184,57 @@

    display: flex; justify-content: space-between; align-items: center; - margin-bottom: 1rem; + margin-bottom: 0.75rem; flex-wrap: wrap; - gap: 0.5rem; + gap: 0.4rem; } .note-tags { display: flex; - gap: 0.5rem; + gap: 0.3rem; flex-wrap: wrap; } +/* Grid View specific adjustments */ +.note-card .visibility-badge { + padding: 0.15rem 0.4rem; + font-size: 0.7rem; +} + +.note-card .folder-badge { + padding: 0.15rem 0.4rem; + font-size: 0.7rem; +} + +.note-card .tag-badge { + padding: 0.15rem 0.4rem; + font-size: 0.7rem; + margin-right: 0.2rem; +} + +.note-card .association-badge { + padding: 0.15rem 0.4rem; + font-size: 0.7rem; +} + +.note-card .note-date { + font-size: 0.75rem; +} + +.note-card .pin-icon { + font-size: 0.85rem; +} + +.note-card .btn-action { + width: 28px; + height: 28px; +} + +.note-card .btn-action svg { + width: 14px; + height: 14px; +} + /* Responsive design */ @media (max-width: 1024px) { .column-folder, @@ -1177,15 +1376,22 @@

    const toggleBtn = document.getElementById('toggle-view'); const tableView = document.getElementById('table-view'); const gridView = document.getElementById('grid-view'); - const viewIcon = toggleBtn.querySelector('.view-icon'); + const iconGrid = toggleBtn.querySelector('.icon-grid'); + const iconList = toggleBtn.querySelector('.icon-list'); + const viewLabel = toggleBtn.querySelector('.view-label'); // Load saved view preference const savedView = localStorage.getItem('notes-view') || 'table'; if (savedView === 'grid') { tableView.style.display = 'none'; gridView.style.display = 'block'; - viewIcon.textContent = '⊞'; - toggleBtn.innerHTML = ' Grid View'; + iconGrid.style.display = 'none'; + iconList.style.display = 'block'; + viewLabel.textContent = 'Grid View'; + } else { + iconGrid.style.display = 'block'; + iconList.style.display = 'none'; + viewLabel.textContent = 'Table View'; } toggleBtn.addEventListener('click', function() { @@ -1193,15 +1399,17 @@

    // Switch to table view tableView.style.display = 'block'; gridView.style.display = 'none'; - viewIcon.textContent = '☰'; - toggleBtn.innerHTML = ' List View'; + iconGrid.style.display = 'block'; + iconList.style.display = 'none'; + viewLabel.textContent = 'Table View'; localStorage.setItem('notes-view', 'table'); } else { // Switch to grid view tableView.style.display = 'none'; gridView.style.display = 'block'; - viewIcon.textContent = '⊞'; - toggleBtn.innerHTML = ' Grid View'; + iconGrid.style.display = 'none'; + iconList.style.display = 'block'; + viewLabel.textContent = 'Grid View'; localStorage.setItem('notes-view', 'grid'); } }); From 9113dc1a69ac87a1c38ca346ab8ad8ee963aadc7 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Sun, 6 Jul 2025 22:29:13 +0200 Subject: [PATCH 07/22] Store YAML frontmatter in notes. --- app.py | 26 +- frontmatter_utils.py | 70 +++ migrate_db.py | 67 +++ migrations/add_cascade_delete_note_links.sql | 20 + .../add_cascade_delete_note_links_sqlite.sql | 25 ++ models.py | 75 +++- requirements.txt | 1 + templates/note_editor.html | 237 ++++++++++- templates/note_mindmap.html | 401 ++++++++++++++++++ templates/note_view.html | 25 +- templates/notes_list.html | 22 +- 11 files changed, 946 insertions(+), 23 deletions(-) create mode 100644 frontmatter_utils.py create mode 100644 migrations/add_cascade_delete_note_links.sql create mode 100644 migrations/add_cascade_delete_note_links_sqlite.sql create mode 100644 templates/note_mindmap.html diff --git a/app.py b/app.py index d5d6013..1f1aa29 100644 --- a/app.py +++ b/app.py @@ -1794,6 +1794,9 @@ def create_note(): company_id=g.user.company_id ) + # Sync metadata from frontmatter if present + note.sync_from_frontmatter() + # Set team_id if visibility is Team if visibility == 'Team' and g.user.team_id: note.team_id = g.user.team_id @@ -1867,11 +1870,11 @@ def view_note(slug): incoming_links = [] for link in note.outgoing_links: - if link.target_note.can_user_view(g.user): + if link.target_note.can_user_view(g.user) and not link.target_note.is_archived: outgoing_links.append(link) for link in note.incoming_links: - if link.source_note.can_user_view(g.user): + if link.source_note.can_user_view(g.user) and not link.source_note.is_archived: incoming_links.append(link) # Get linkable notes for the modal @@ -1896,6 +1899,22 @@ def view_note(slug): can_edit=note.can_user_edit(g.user)) +@app.route('/notes//mindmap') +@login_required +@company_required +def view_note_mindmap(slug): + """View a note as a mind map""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_view(g.user): + abort(403) + + return render_template('note_mindmap.html', + title=f"{note.title} - Mind Map", + note=note) + + @app.route('/notes//edit', methods=['GET', 'POST']) @login_required @company_required @@ -1938,6 +1957,9 @@ def edit_note(slug): note.folder = folder if folder else None note.tags = ','.join(tag_list) if tag_list else None + # Sync metadata from frontmatter if present + note.sync_from_frontmatter() + # Update team_id if visibility is Team if visibility == 'Team' and g.user.team_id: note.team_id = g.user.team_id diff --git a/frontmatter_utils.py b/frontmatter_utils.py new file mode 100644 index 0000000..b8aa906 --- /dev/null +++ b/frontmatter_utils.py @@ -0,0 +1,70 @@ +import yaml +import re +from datetime import datetime + +def parse_frontmatter(content): + """ + Parse YAML frontmatter from markdown content. + Returns a tuple of (metadata dict, content without frontmatter) + """ + if not content or not content.strip().startswith('---'): + return {}, content + + # Match frontmatter pattern + pattern = r'^---\s*\n(.*?)\n---\s*\n(.*)$' + match = re.match(pattern, content, re.DOTALL) + + if not match: + return {}, content + + try: + # Parse YAML frontmatter + metadata = yaml.safe_load(match.group(1)) or {} + content_body = match.group(2) + return metadata, content_body + except yaml.YAMLError: + # If YAML parsing fails, return original content + return {}, content + +def create_frontmatter(metadata): + """ + Create YAML frontmatter from metadata dict. + """ + if not metadata: + return "" + + # Filter out None values and empty strings + filtered_metadata = {k: v for k, v in metadata.items() if v is not None and v != ''} + + if not filtered_metadata: + return "" + + return f"---\n{yaml.dump(filtered_metadata, default_flow_style=False, sort_keys=False)}---\n\n" + +def update_frontmatter(content, metadata): + """ + Update or add frontmatter to content. + """ + _, body = parse_frontmatter(content) + frontmatter = create_frontmatter(metadata) + return frontmatter + body + +def extract_title_from_content(content): + """ + Extract title from content, checking frontmatter first, then first line. + """ + metadata, body = parse_frontmatter(content) + + # Check if title is in frontmatter + if metadata.get('title'): + return metadata['title'] + + # Otherwise extract from first line of body + lines = body.strip().split('\n') + for line in lines: + line = line.strip() + if line: + # Remove markdown headers if present + return re.sub(r'^#+\s*', '', line) + + return 'Untitled Note' \ No newline at end of file diff --git a/migrate_db.py b/migrate_db.py index d80a97a..5ba69d7 100644 --- a/migrate_db.py +++ b/migrate_db.py @@ -77,6 +77,7 @@ def run_all_migrations(db_path=None): migrate_dashboard_system(db_path) migrate_comment_system(db_path) migrate_notes_system(db_path) + update_note_link_cascade(db_path) # Run PostgreSQL-specific migrations if applicable if FLASK_AVAILABLE: @@ -1753,6 +1754,72 @@ def migrate_notes_system(db_file=None): conn.close() +def update_note_link_cascade(db_path): + """Update note_link table to ensure CASCADE delete is enabled.""" + print("Checking note_link cascade delete constraints...") + + try: + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # Check if note_link table exists + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='note_link'") + if not cursor.fetchone(): + print("note_link table does not exist, skipping cascade update") + return + + # Check current foreign key constraints + cursor.execute("PRAGMA foreign_key_list(note_link)") + fk_info = cursor.fetchall() + + # Check if CASCADE is already set + has_cascade = any('CASCADE' in str(fk) for fk in fk_info) + + if not has_cascade: + print("Updating note_link table with CASCADE delete...") + + # SQLite doesn't support ALTER TABLE for foreign keys, so recreate the table + cursor.execute(""" + CREATE TABLE note_link_temp ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + source_note_id INTEGER NOT NULL, + target_note_id INTEGER NOT NULL, + link_type VARCHAR(50) DEFAULT 'related', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by_id INTEGER NOT NULL, + FOREIGN KEY (source_note_id) REFERENCES note(id) ON DELETE CASCADE, + FOREIGN KEY (target_note_id) REFERENCES note(id) ON DELETE CASCADE, + FOREIGN KEY (created_by_id) REFERENCES user(id), + UNIQUE(source_note_id, target_note_id) + ) + """) + + # Copy data + cursor.execute("INSERT INTO note_link_temp SELECT * FROM note_link") + + # Drop old table and rename new one + cursor.execute("DROP TABLE note_link") + cursor.execute("ALTER TABLE note_link_temp RENAME TO note_link") + + # Recreate indexes + cursor.execute("CREATE INDEX idx_note_link_source ON note_link(source_note_id)") + cursor.execute("CREATE INDEX idx_note_link_target ON note_link(target_note_id)") + + print("note_link table updated with CASCADE delete") + else: + print("note_link table already has CASCADE delete") + + conn.commit() + + except Exception as e: + print(f"Error updating note_link cascade: {e}") + if conn: + conn.rollback() + finally: + if conn: + conn.close() + + def main(): """Main function with command line interface.""" parser = argparse.ArgumentParser(description='TimeTrack Database Migration Tool') diff --git a/migrations/add_cascade_delete_note_links.sql b/migrations/add_cascade_delete_note_links.sql new file mode 100644 index 0000000..697aa16 --- /dev/null +++ b/migrations/add_cascade_delete_note_links.sql @@ -0,0 +1,20 @@ +-- Migration to add CASCADE delete to note_link foreign keys +-- This ensures that when a note is deleted, all links to/from it are also deleted + +-- For PostgreSQL +-- Drop existing foreign key constraints +ALTER TABLE note_link DROP CONSTRAINT IF EXISTS note_link_source_note_id_fkey; +ALTER TABLE note_link DROP CONSTRAINT IF EXISTS note_link_target_note_id_fkey; + +-- Add new foreign key constraints with CASCADE +ALTER TABLE note_link + ADD CONSTRAINT note_link_source_note_id_fkey + FOREIGN KEY (source_note_id) + REFERENCES note(id) + ON DELETE CASCADE; + +ALTER TABLE note_link + ADD CONSTRAINT note_link_target_note_id_fkey + FOREIGN KEY (target_note_id) + REFERENCES note(id) + ON DELETE CASCADE; \ No newline at end of file diff --git a/migrations/add_cascade_delete_note_links_sqlite.sql b/migrations/add_cascade_delete_note_links_sqlite.sql new file mode 100644 index 0000000..3816bfe --- /dev/null +++ b/migrations/add_cascade_delete_note_links_sqlite.sql @@ -0,0 +1,25 @@ +-- SQLite migration for cascade delete on note_link +-- SQLite doesn't support ALTER TABLE for foreign keys, so we need to recreate the table + +-- Create new table with CASCADE delete +CREATE TABLE note_link_new ( + id INTEGER PRIMARY KEY, + source_note_id INTEGER NOT NULL, + target_note_id INTEGER NOT NULL, + link_type VARCHAR(50) DEFAULT 'related', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + created_by_id INTEGER NOT NULL, + FOREIGN KEY (source_note_id) REFERENCES note(id) ON DELETE CASCADE, + FOREIGN KEY (target_note_id) REFERENCES note(id) ON DELETE CASCADE, + FOREIGN KEY (created_by_id) REFERENCES user(id), + UNIQUE(source_note_id, target_note_id) +); + +-- Copy data from old table +INSERT INTO note_link_new SELECT * FROM note_link; + +-- Drop old table +DROP TABLE note_link; + +-- Rename new table +ALTER TABLE note_link_new RENAME TO note_link; \ No newline at end of file diff --git a/models.py b/models.py index 61c232f..65fc813 100644 --- a/models.py +++ b/models.py @@ -1368,7 +1368,12 @@ def get_preview(self, length=200): """Get a plain text preview of the note content""" # Strip markdown formatting for preview import re - text = self.content + from frontmatter_utils import parse_frontmatter + + # Extract body content without frontmatter + _, body = parse_frontmatter(self.content) + text = body + # Remove headers text = re.sub(r'^#+\s+', '', text, flags=re.MULTILINE) # Remove emphasis @@ -1390,30 +1395,84 @@ def render_html(self): """Render markdown content to HTML""" try: import markdown + from frontmatter_utils import parse_frontmatter + # Extract body content without frontmatter + _, body = parse_frontmatter(self.content) # Use extensions for better markdown support - html = markdown.markdown(self.content, extensions=['extra', 'codehilite', 'toc']) + html = markdown.markdown(body, extensions=['extra', 'codehilite', 'toc']) return html except ImportError: # Fallback if markdown not installed return f'
    {self.content}
    ' + + def get_frontmatter(self): + """Get frontmatter metadata from content""" + from frontmatter_utils import parse_frontmatter + metadata, _ = parse_frontmatter(self.content) + return metadata + + def update_frontmatter(self): + """Update content with current metadata as frontmatter""" + from frontmatter_utils import update_frontmatter + metadata = { + 'title': self.title, + 'visibility': self.visibility.value.lower(), + 'folder': self.folder, + 'tags': self.get_tags_list() if self.tags else None, + 'project': self.project.code if self.project else None, + 'task_id': self.task_id, + 'pinned': self.is_pinned if self.is_pinned else None, + 'created': self.created_at.isoformat() if self.created_at else None, + 'updated': self.updated_at.isoformat() if self.updated_at else None, + 'author': self.created_by.username if self.created_by else None + } + # Remove None values + metadata = {k: v for k, v in metadata.items() if v is not None} + self.content = update_frontmatter(self.content, metadata) + + def sync_from_frontmatter(self): + """Update model fields from frontmatter in content""" + from frontmatter_utils import parse_frontmatter + metadata, _ = parse_frontmatter(self.content) + + if metadata: + # Update fields from frontmatter + if 'title' in metadata: + self.title = metadata['title'] + if 'visibility' in metadata: + try: + self.visibility = NoteVisibility[metadata['visibility'].upper()] + except KeyError: + pass + if 'folder' in metadata: + self.folder = metadata['folder'] + if 'tags' in metadata: + if isinstance(metadata['tags'], list): + self.set_tags_list(metadata['tags']) + elif isinstance(metadata['tags'], str): + self.tags = metadata['tags'] + if 'pinned' in metadata: + self.is_pinned = bool(metadata['pinned']) class NoteLink(db.Model): """Links between notes for creating relationships""" id = db.Column(db.Integer, primary_key=True) - # Source and target notes - source_note_id = db.Column(db.Integer, db.ForeignKey('note.id'), nullable=False) - target_note_id = db.Column(db.Integer, db.ForeignKey('note.id'), nullable=False) + # Source and target notes with cascade deletion + source_note_id = db.Column(db.Integer, db.ForeignKey('note.id', ondelete='CASCADE'), nullable=False) + target_note_id = db.Column(db.Integer, db.ForeignKey('note.id', ondelete='CASCADE'), nullable=False) # Link metadata link_type = db.Column(db.String(50), default='related') # related, parent, child, etc. created_at = db.Column(db.DateTime, default=datetime.now) created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) - # Relationships - source_note = db.relationship('Note', foreign_keys=[source_note_id], backref='outgoing_links') - target_note = db.relationship('Note', foreign_keys=[target_note_id], backref='incoming_links') + # Relationships with cascade deletion + source_note = db.relationship('Note', foreign_keys=[source_note_id], + backref=db.backref('outgoing_links', cascade='all, delete-orphan')) + target_note = db.relationship('Note', foreign_keys=[target_note_id], + backref=db.backref('incoming_links', cascade='all, delete-orphan')) created_by = db.relationship('User', foreign_keys=[created_by_id]) # Unique constraint to prevent duplicate links diff --git a/requirements.txt b/requirements.txt index 7df6e54..82c0062 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,4 @@ xlsxwriter==3.1.2 Flask-Mail==0.9.1 psycopg2-binary==2.9.9 markdown==3.4.4 +PyYAML==6.0.1 diff --git a/templates/note_editor.html b/templates/note_editor.html index b3ded1c..6b02cec 100644 --- a/templates/note_editor.html +++ b/templates/note_editor.html @@ -89,6 +89,10 @@

    Note Settings

    + + @@ -635,6 +639,25 @@

    Linked Notes

    // Global Ace Editor instance let aceEditor; +// Toggle frontmatter visibility +function toggleFrontmatter() { + if (!aceEditor) return; + + const content = aceEditor.getValue(); + + if (content.trim().startsWith('---')) { + // Frontmatter exists, just move cursor to it + aceEditor.moveCursorTo(0, 0); + aceEditor.focus(); + } else { + // Add frontmatter + const newContent = updateContentFrontmatter(content); + aceEditor.setValue(newContent, -1); + aceEditor.moveCursorTo(0, 0); + aceEditor.focus(); + } +} + // Markdown toolbar functions for Ace Editor function insertMarkdown(before, after) { if (!aceEditor) return; @@ -666,14 +689,39 @@

    Linked Notes

    syncContentAndUpdatePreview(); } -// Extract title from first line of content +// Extract title from content (frontmatter or first line) function extractTitleFromContent(content) { + // Check for frontmatter first + if (content.trim().startsWith('---')) { + const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/); + if (frontmatterMatch) { + const frontmatterContent = frontmatterMatch[1]; + const titleMatch = frontmatterContent.match(/title:\s*(.+)/); + if (titleMatch) { + // Remove quotes if present + return titleMatch[1].replace(/^["']|["']$/g, '').trim(); + } + } + } + + // Otherwise extract from first line const lines = content.split('\n'); let firstLine = ''; - // Find the first non-empty line - for (let line of lines) { - const trimmed = line.trim(); + // Skip frontmatter if present + let skipUntil = 0; + if (lines[0].trim() === '---') { + for (let i = 1; i < lines.length; i++) { + if (lines[i].trim() === '---') { + skipUntil = i + 1; + break; + } + } + } + + // Find the first non-empty line after frontmatter + for (let i = skipUntil; i < lines.length; i++) { + const trimmed = lines[i].trim(); if (trimmed) { // Remove markdown headers if present firstLine = trimmed.replace(/^#+\s*/, ''); @@ -685,14 +733,101 @@

    Linked Notes

    return firstLine || 'Untitled Note'; } +// Update or create frontmatter in content +function updateContentFrontmatter(content) { + const settings = { + visibility: document.getElementById('visibility').value.toLowerCase(), + folder: document.getElementById('folder').value || undefined, + tags: document.getElementById('tags').value ? + document.getElementById('tags').value.split(',').map(t => t.trim()).filter(t => t) : undefined, + project: document.getElementById('project_id').selectedOptions[0]?.text.split(' - ')[0] || undefined, + task_id: parseInt(document.getElementById('task_id').value) || undefined, + pinned: false + }; + + // Remove undefined values + Object.keys(settings).forEach(key => settings[key] === undefined && delete settings[key]); + + // Parse existing frontmatter + let body = content; + let existingFrontmatter = {}; + + if (content.trim().startsWith('---')) { + const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/); + if (match) { + try { + // Simple YAML parsing for our use case + const yamlContent = match[1]; + yamlContent.split('\n').forEach(line => { + const colonIndex = line.indexOf(':'); + if (colonIndex > 0) { + const key = line.substring(0, colonIndex).trim(); + const value = line.substring(colonIndex + 1).trim(); + existingFrontmatter[key] = value.replace(/^["']|["']$/g, ''); + } + }); + body = match[2]; + } catch (e) { + console.error('Error parsing frontmatter:', e); + } + } + } + + // Merge settings with existing frontmatter + const frontmatter = { ...existingFrontmatter, ...settings }; + + // Add title from content or form field + const titleField = document.getElementById('title').value; + if (titleField && titleField !== 'Untitled Note') { + frontmatter.title = titleField; + } else { + frontmatter.title = extractTitleFromContent(body); + } + + // Build new frontmatter + let yamlContent = '---\n'; + Object.entries(frontmatter).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + if (Array.isArray(value)) { + yamlContent += `${key}:\n`; + value.forEach(item => { + yamlContent += ` - ${item}\n`; + }); + } else if (typeof value === 'string' && (value.includes(':') || value.includes('"'))) { + yamlContent += `${key}: "${value}"\n`; + } else { + yamlContent += `${key}: ${value}\n`; + } + } + }); + yamlContent += '---\n\n'; + + return yamlContent + body; +} + // Sync Ace Editor content with hidden textarea and update preview -function syncContentAndUpdatePreview() { +let frontmatterUpdateTimer; +function syncContentAndUpdatePreview(updateFrontmatter = true) { if (!aceEditor) return; - const content = aceEditor.getValue(); + let content = aceEditor.getValue(); + + // Only update frontmatter when settings change, not on every keystroke + if (updateFrontmatter) { + clearTimeout(frontmatterUpdateTimer); + frontmatterUpdateTimer = setTimeout(() => { + const newContent = updateContentFrontmatter(aceEditor.getValue()); + if (aceEditor.getValue() !== newContent) { + const currentPosition = aceEditor.getCursorPosition(); + aceEditor.setValue(newContent, -1); + aceEditor.moveCursorToPosition(currentPosition); + } + }, 2000); // Wait 2 seconds after typing stops + } + document.getElementById('content').value = content; - // Update title from first line + // Update title from content const title = extractTitleFromContent(content); document.getElementById('title').value = title; @@ -703,9 +838,61 @@

    Linked Notes

    headerTitle.textContent = title ? (isEdit ? `Edit: ${title}` : title) : (isEdit ? 'Edit Note' : 'Create Note'); } + // Sync settings from frontmatter + syncSettingsFromFrontmatter(content); + updatePreview(); } +// Sync settings UI from frontmatter +function syncSettingsFromFrontmatter(content) { + if (!content.trim().startsWith('---')) return; + + const match = content.match(/^---\s*\n([\s\S]*?)\n---/); + if (!match) return; + + const yamlContent = match[1]; + const frontmatter = {}; + + // Simple YAML parsing + yamlContent.split('\n').forEach(line => { + const colonIndex = line.indexOf(':'); + if (colonIndex > 0) { + const key = line.substring(0, colonIndex).trim(); + let value = line.substring(colonIndex + 1).trim(); + + // Handle arrays (tags) + if (key === 'tags' && !value) { + // Multi-line array, skip for now + return; + } + + value = value.replace(/^["']|["']$/g, ''); + frontmatter[key] = value; + } + }); + + // Update UI elements + if (frontmatter.visibility) { + const visibilitySelect = document.getElementById('visibility'); + const capitalizedVisibility = frontmatter.visibility.charAt(0).toUpperCase() + frontmatter.visibility.slice(1); + for (let option of visibilitySelect.options) { + if (option.value === capitalizedVisibility) { + visibilitySelect.value = capitalizedVisibility; + break; + } + } + } + + if (frontmatter.folder !== undefined) { + document.getElementById('folder').value = frontmatter.folder; + } + + if (frontmatter.tags !== undefined) { + document.getElementById('tags').value = frontmatter.tags; + } +} + // Live preview update let previewTimer; function updatePreview() { @@ -747,7 +934,7 @@

    Linked Notes

    // Set theme (use github theme for light mode) aceEditor.setTheme("ace/theme/github"); - // Set markdown mode + // Set markdown mode (which includes YAML frontmatter highlighting) aceEditor.session.setMode("ace/mode/markdown"); // Configure editor options @@ -770,15 +957,43 @@

    Linked Notes

    const initialContent = document.getElementById('content').value; aceEditor.setValue(initialContent, -1); // -1 moves cursor to start - // If editing and has content, extract title + // If editing and has content, sync from frontmatter if (initialContent) { - const title = extractTitleFromContent(initialContent); + // If editing existing note without frontmatter, add it + if (!initialContent.trim().startsWith('---')) { + const newContent = updateContentFrontmatter(initialContent); + aceEditor.setValue(newContent, -1); + } + syncSettingsFromFrontmatter(aceEditor.getValue()); + const title = extractTitleFromContent(aceEditor.getValue()); document.getElementById('title').value = title; } // Listen for changes in Ace Editor aceEditor.on('change', function() { - syncContentAndUpdatePreview(); + syncContentAndUpdatePreview(false); // Don't update frontmatter on every keystroke + }); + + // If this is a new note, add initial frontmatter + if (!initialContent || initialContent.trim() === '') { + const newContent = updateContentFrontmatter('# New Note\n\nStart writing here...'); + aceEditor.setValue(newContent, -1); + syncContentAndUpdatePreview(false); + } + + // Listen for changes in settings to update frontmatter + ['visibility', 'folder', 'tags', 'project_id', 'task_id'].forEach(id => { + const element = document.getElementById(id); + if (element) { + element.addEventListener('change', function() { + // Force immediate frontmatter update when settings change + const content = updateContentFrontmatter(aceEditor.getValue()); + const currentPosition = aceEditor.getCursorPosition(); + aceEditor.setValue(content, -1); + aceEditor.moveCursorToPosition(currentPosition); + syncContentAndUpdatePreview(false); + }); + } }); // Handle form submission - ensure content is synced diff --git a/templates/note_mindmap.html b/templates/note_mindmap.html new file mode 100644 index 0000000..7ffbc3d --- /dev/null +++ b/templates/note_mindmap.html @@ -0,0 +1,401 @@ +{% extends "layout.html" %} + +{% block content %} +
    +
    +

    {{ note.title }} - Mind Map View

    +
    + + + + Back to Note + +
    +
    + +
    + +
    +
    + + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/note_view.html b/templates/note_view.html index d6f2b86..2d33e0a 100644 --- a/templates/note_view.html +++ b/templates/note_view.html @@ -15,10 +15,24 @@

    {{ note.title }}

    {% if note.updated_at > note.created_at %} · Updated {{ note.updated_at|format_date }} {% endif %} + {% if note.is_pinned %} + 📌 Pinned + {% endif %}
    + + + + + + + + + + Mind Map + {% if note.can_user_edit(g.user) %} Edit
    {{ note.title }}

    Tags: - {% for tag in note.tags %} + {% for tag in note.get_tags_list() %} {{ tag }} {% endfor %} @@ -429,6 +443,15 @@

    Link to Another Note

    font-style: italic; } +.pin-badge { + background: #fff3cd; + color: #856404; + padding: 0.2rem 0.5rem; + border-radius: 4px; + font-size: 0.85rem; + font-weight: 500; +} + /* Modal styles */ .modal-content { max-width: 500px; diff --git a/templates/notes_list.html b/templates/notes_list.html index dc09961..5a59bbb 100644 --- a/templates/notes_list.html +++ b/templates/notes_list.html @@ -248,6 +248,16 @@

    Visibility

    + + + + + + + + + + {% if note.can_user_edit(g.user) %} @@ -328,6 +338,16 @@

    + + + + + + + + + + {% if note.can_user_edit(g.user) %} @@ -1006,7 +1026,7 @@

    } .column-actions { - width: 120px; + width: 160px; } .note-actions { From f4b8664fd56093a3e32011fcfd80c071f2a44dcc Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Sun, 6 Jul 2025 22:46:31 +0200 Subject: [PATCH 08/22] Add File Download for notes. --- app.py | 281 ++++++++++++++++++++++++++++++++++++++ templates/note_view.html | 108 ++++++++++++++- templates/notes_list.html | 144 ++++++++++++++++++- 3 files changed, 527 insertions(+), 6 deletions(-) diff --git a/app.py b/app.py index 1f1aa29..36fcf31 100644 --- a/app.py +++ b/app.py @@ -14,6 +14,7 @@ import os import csv import io +import re import pandas as pd from sqlalchemy import func from functools import wraps @@ -1915,6 +1916,286 @@ def view_note_mindmap(slug): note=note) +@app.route('/notes//download/') +@login_required +@company_required +def download_note(slug, format): + """Download a note in various formats""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_view(g.user): + abort(403) + + # Prepare filename + safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', note.title) + timestamp = datetime.now().strftime('%Y%m%d') + + if format == 'md': + # Download as Markdown with frontmatter + content = note.content + response = Response(content, mimetype='text/markdown') + response.headers['Content-Disposition'] = f'attachment; filename="{safe_filename}_{timestamp}.md"' + return response + + elif format == 'html': + # Download as HTML + html_content = f""" + + + + {note.title} + + + + + {note.render_html()} + +""" + response = Response(html_content, mimetype='text/html') + response.headers['Content-Disposition'] = f'attachment; filename="{safe_filename}_{timestamp}.html"' + return response + + elif format == 'txt': + # Download as plain text + from frontmatter_utils import parse_frontmatter + metadata, body = parse_frontmatter(note.content) + + # Create plain text version + text_content = f"{note.title}\n{'=' * len(note.title)}\n\n" + text_content += f"Author: {note.created_by.username}\n" + text_content += f"Created: {note.created_at.strftime('%Y-%m-%d %H:%M')}\n" + text_content += f"Updated: {note.updated_at.strftime('%Y-%m-%d %H:%M')}\n" + text_content += f"Visibility: {note.visibility.value}\n" + if note.folder: + text_content += f"Folder: {note.folder}\n" + if note.tags: + text_content += f"Tags: {note.tags}\n" + text_content += "\n" + "-" * 40 + "\n\n" + + # Remove markdown formatting + text_body = body + # Remove headers markdown + text_body = re.sub(r'^#+\s+', '', text_body, flags=re.MULTILINE) + # Remove emphasis + text_body = re.sub(r'\*{1,2}([^\*]+)\*{1,2}', r'\1', text_body) + text_body = re.sub(r'_{1,2}([^_]+)_{1,2}', r'\1', text_body) + # Remove links but keep text + text_body = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text_body) + # Remove images + text_body = re.sub(r'!\[([^\]]*)\]\([^\)]+\)', r'[Image: \1]', text_body) + # Remove code blocks markers + text_body = re.sub(r'```[^`]*```', lambda m: m.group(0).replace('```', ''), text_body, flags=re.DOTALL) + text_body = re.sub(r'`([^`]+)`', r'\1', text_body) + + text_content += text_body + + response = Response(text_content, mimetype='text/plain') + response.headers['Content-Disposition'] = f'attachment; filename="{safe_filename}_{timestamp}.txt"' + return response + + else: + abort(404) + + +@app.route('/notes/download-bulk', methods=['POST']) +@login_required +@company_required +def download_notes_bulk(): + """Download multiple notes as a zip file""" + import zipfile + import tempfile + + note_ids = request.form.getlist('note_ids[]') + format = request.form.get('format', 'md') + + if not note_ids: + flash('No notes selected for download', 'error') + return redirect(url_for('notes_list')) + + # Create a temporary file for the zip + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip') + + try: + with zipfile.ZipFile(temp_file.name, 'w') as zipf: + for note_id in note_ids: + note = Note.query.filter_by(id=int(note_id), company_id=g.user.company_id).first() + if note and note.can_user_view(g.user): + # Get content based on format + safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', note.title) + + if format == 'md': + content = note.content + filename = f"{safe_filename}.md" + elif format == 'html': + content = f""" + + + + {note.title} + + + +

    {note.title}

    + {note.render_html()} + +""" + filename = f"{safe_filename}.html" + else: # txt + from frontmatter_utils import parse_frontmatter + metadata, body = parse_frontmatter(note.content) + content = f"{note.title}\n{'=' * len(note.title)}\n\n{body}" + filename = f"{safe_filename}.txt" + + # Add file to zip + zipf.writestr(filename, content) + + # Send the zip file + temp_file.seek(0) + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + + return send_file( + temp_file.name, + mimetype='application/zip', + as_attachment=True, + download_name=f'notes_{timestamp}.zip' + ) + + finally: + # Clean up temp file after sending + import os + os.unlink(temp_file.name) + + +@app.route('/notes/folder//download/') +@login_required +@company_required +def download_folder(folder_path, format): + """Download all notes in a folder as a zip file""" + import zipfile + import tempfile + + # Decode folder path (replace URL encoding) + from urllib.parse import unquote + folder_path = unquote(folder_path) + + # Get all notes in this folder + notes = Note.query.filter_by( + company_id=g.user.company_id, + folder=folder_path, + is_archived=False + ).all() + + # Filter notes user can view + viewable_notes = [note for note in notes if note.can_user_view(g.user)] + + if not viewable_notes: + flash('No notes found in this folder or you don\'t have permission to view them.', 'warning') + return redirect(url_for('notes_list')) + + # Create a temporary file for the zip + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip') + + try: + with zipfile.ZipFile(temp_file.name, 'w') as zipf: + for note in viewable_notes: + # Get content based on format + safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', note.title) + + if format == 'md': + content = note.content + filename = f"{safe_filename}.md" + elif format == 'html': + content = f""" + + + + {note.title} + + + + + {note.render_html()} + +""" + filename = f"{safe_filename}.html" + else: # txt + from frontmatter_utils import parse_frontmatter + metadata, body = parse_frontmatter(note.content) + # Remove markdown formatting + text_body = body + text_body = re.sub(r'^#+\s+', '', text_body, flags=re.MULTILINE) + text_body = re.sub(r'\*{1,2}([^\*]+)\*{1,2}', r'\1', text_body) + text_body = re.sub(r'_{1,2}([^_]+)_{1,2}', r'\1', text_body) + text_body = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text_body) + text_body = re.sub(r'!\[([^\]]*)\]\([^\)]+\)', r'[Image: \1]', text_body) + text_body = re.sub(r'```[^`]*```', lambda m: m.group(0).replace('```', ''), text_body, flags=re.DOTALL) + text_body = re.sub(r'`([^`]+)`', r'\1', text_body) + + content = f"{note.title}\n{'=' * len(note.title)}\n\n" + content += f"Author: {note.created_by.username}\n" + content += f"Created: {note.created_at.strftime('%Y-%m-%d %H:%M')}\n" + content += f"Folder: {note.folder}\n\n" + content += "-" * 40 + "\n\n" + content += text_body + filename = f"{safe_filename}.txt" + + # Add file to zip + zipf.writestr(filename, content) + + # Send the zip file + temp_file.seek(0) + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + safe_folder_name = re.sub(r'[^a-zA-Z0-9_-]', '_', folder_path.replace('/', '_')) + + return send_file( + temp_file.name, + mimetype='application/zip', + as_attachment=True, + download_name=f'{safe_folder_name}_notes_{timestamp}.zip' + ) + + finally: + # Clean up temp file after sending + os.unlink(temp_file.name) + + @app.route('/notes//edit', methods=['GET', 'POST']) @login_required @company_required diff --git a/templates/note_view.html b/templates/note_view.html index 2d33e0a..040e44d 100644 --- a/templates/note_view.html +++ b/templates/note_view.html @@ -22,6 +22,38 @@

    {{ note.title }}

    + @@ -452,6 +484,64 @@

    Link to Another Note

    font-weight: 500; } +/* Dropdown styles */ +.dropdown { + position: relative; +} + +.dropdown-toggle::after { + content: ""; + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + min-width: 200px; + padding: 0.5rem 0; + margin: 0.125rem 0 0; + background-color: white; + border: 1px solid #dee2e6; + border-radius: 0.375rem; + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); +} + +.dropdown-menu.show { + display: block; +} + +.dropdown-item { + display: flex; + align-items: center; + width: 100%; + padding: 0.5rem 1rem; + color: #212529; + text-align: inherit; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border: 0; +} + +.dropdown-item:hover { + background-color: #f8f9fa; + color: #212529; + text-decoration: none; +} + +.dropdown-item svg { + flex-shrink: 0; +} + /* Modal styles */ .modal-content { max-width: 500px; @@ -486,8 +576,22 @@

    Link to Another Note

    {% endblock %} \ No newline at end of file diff --git a/templates/notes_list.html b/templates/notes_list.html index 5a59bbb..379fcba 100644 --- a/templates/notes_list.html +++ b/templates/notes_list.html @@ -258,6 +258,12 @@

    Visibility

    + + + + + + {% if note.can_user_edit(g.user) %} @@ -348,6 +354,12 @@

    + + + + + + {% if note.can_user_edit(g.user) %} @@ -379,6 +391,13 @@

    + + + - - - - {note.render_html()} - -""" - response = Response(html_content, mimetype='text/html') - response.headers['Content-Disposition'] = f'attachment; filename="{safe_filename}_{timestamp}.html"' - return response - - elif format == 'txt': - # Download as plain text - metadata, body = parse_frontmatter(note.content) - - # Create plain text version - text_content = f"{note.title}\n{'=' * len(note.title)}\n\n" - text_content += f"Author: {note.created_by.username}\n" - text_content += f"Created: {note.created_at.strftime('%Y-%m-%d %H:%M')}\n" - text_content += f"Updated: {note.updated_at.strftime('%Y-%m-%d %H:%M')}\n" - text_content += f"Visibility: {note.visibility.value}\n" - if note.folder: - text_content += f"Folder: {note.folder}\n" - if note.tags: - text_content += f"Tags: {note.tags}\n" - text_content += "\n" + "-" * 40 + "\n\n" - - # Remove markdown formatting - text_body = body - # Remove headers markdown - text_body = re.sub(r'^#+\s+', '', text_body, flags=re.MULTILINE) - # Remove emphasis - text_body = re.sub(r'\*{1,2}([^\*]+)\*{1,2}', r'\1', text_body) - text_body = re.sub(r'_{1,2}([^_]+)_{1,2}', r'\1', text_body) - # Remove links but keep text - text_body = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text_body) - # Remove images - text_body = re.sub(r'!\[([^\]]*)\]\([^\)]+\)', r'[Image: \1]', text_body) - # Remove code blocks markers - text_body = re.sub(r'```[^`]*```', lambda m: m.group(0).replace('```', ''), text_body, flags=re.DOTALL) - text_body = re.sub(r'`([^`]+)`', r'\1', text_body) - - text_content += text_body - - response = Response(text_content, mimetype='text/plain') - response.headers['Content-Disposition'] = f'attachment; filename="{safe_filename}_{timestamp}.txt"' - return response - - else: - abort(404) - - -@app.route('/notes/download-bulk', methods=['POST']) -@login_required -@company_required -def download_notes_bulk(): - """Download multiple notes as a zip file""" - - note_ids = request.form.getlist('note_ids[]') - format = request.form.get('format', 'md') - - if not note_ids: - flash('No notes selected for download', 'error') - return redirect(url_for('notes_list')) - - # Create a temporary file for the zip - temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip') - - try: - with zipfile.ZipFile(temp_file.name, 'w') as zipf: - for note_id in note_ids: - note = Note.query.filter_by(id=int(note_id), company_id=g.user.company_id).first() - if note and note.can_user_view(g.user): - # Get content based on format - safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', note.title) - - if format == 'md': - content = note.content - filename = f"{safe_filename}.md" - elif format == 'html': - content = f""" - - - - {note.title} - - - -

    {note.title}

    - {note.render_html()} - -""" - filename = f"{safe_filename}.html" - else: # txt - metadata, body = parse_frontmatter(note.content) - content = f"{note.title}\n{'=' * len(note.title)}\n\n{body}" - filename = f"{safe_filename}.txt" - - # Add file to zip - zipf.writestr(filename, content) - - # Send the zip file - temp_file.seek(0) - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') - - return send_file( - temp_file.name, - mimetype='application/zip', - as_attachment=True, - download_name=f'notes_{timestamp}.zip' - ) - - finally: - # Clean up temp file after sending - os.unlink(temp_file.name) - - -@app.route('/notes/folder//download/') -@login_required -@company_required -def download_folder(folder_path, format): - """Download all notes in a folder as a zip file""" - - # Decode folder path (replace URL encoding) - folder_path = unquote(folder_path) - - # Get all notes in this folder - notes = Note.query.filter_by( - company_id=g.user.company_id, - folder=folder_path, - is_archived=False - ).all() - - # Filter notes user can view - viewable_notes = [note for note in notes if note.can_user_view(g.user)] - - if not viewable_notes: - flash('No notes found in this folder or you don\'t have permission to view them.', 'warning') - return redirect(url_for('notes_list')) - - # Create a temporary file for the zip - temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip') - - try: - with zipfile.ZipFile(temp_file.name, 'w') as zipf: - for note in viewable_notes: - # Get content based on format - safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', note.title) - - if format == 'md': - content = note.content - filename = f"{safe_filename}.md" - elif format == 'html': - content = f""" - - - - {note.title} - - - - - {note.render_html()} - -""" - filename = f"{safe_filename}.html" - else: # txt - metadata, body = parse_frontmatter(note.content) - # Remove markdown formatting - text_body = body - text_body = re.sub(r'^#+\s+', '', text_body, flags=re.MULTILINE) - text_body = re.sub(r'\*{1,2}([^\*]+)\*{1,2}', r'\1', text_body) - text_body = re.sub(r'_{1,2}([^_]+)_{1,2}', r'\1', text_body) - text_body = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text_body) - text_body = re.sub(r'!\[([^\]]*)\]\([^\)]+\)', r'[Image: \1]', text_body) - text_body = re.sub(r'```[^`]*```', lambda m: m.group(0).replace('```', ''), text_body, flags=re.DOTALL) - text_body = re.sub(r'`([^`]+)`', r'\1', text_body) - - content = f"{note.title}\n{'=' * len(note.title)}\n\n" - content += f"Author: {note.created_by.username}\n" - content += f"Created: {note.created_at.strftime('%Y-%m-%d %H:%M')}\n" - content += f"Folder: {note.folder}\n\n" - content += "-" * 40 + "\n\n" - content += text_body - filename = f"{safe_filename}.txt" - - # Add file to zip - zipf.writestr(filename, content) - - # Send the zip file - temp_file.seek(0) - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') - safe_folder_name = re.sub(r'[^a-zA-Z0-9_-]', '_', folder_path.replace('/', '_')) - - return send_file( - temp_file.name, - mimetype='application/zip', - as_attachment=True, - download_name=f'{safe_folder_name}_notes_{timestamp}.zip' - ) - - finally: - # Clean up temp file after sending - os.unlink(temp_file.name) - - -@app.route('/notes//edit', methods=['GET', 'POST']) -@login_required -@company_required -def edit_note(slug): - """Edit an existing note""" - note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() - - # Check permissions - if not note.can_user_edit(g.user): - abort(403) - - if request.method == 'POST': - title = request.form.get('title', '').strip() - content = request.form.get('content', '').strip() - visibility = request.form.get('visibility', 'Private') - folder = request.form.get('folder', '').strip() - tags = request.form.get('tags', '').strip() - project_id = request.form.get('project_id') - task_id = request.form.get('task_id') - - # Validate - if not title: - flash('Title is required', 'error') - return redirect(url_for('edit_note', slug=slug)) - - if not content: - flash('Content is required', 'error') - return redirect(url_for('edit_note', slug=slug)) - - try: - # Parse tags - tag_list = [] - if tags: - tag_list = [tag.strip() for tag in tags.split(',') if tag.strip()] - - # Update note - note.title = title - note.content = content - note.visibility = NoteVisibility[visibility.upper()] # Convert to uppercase for enum access - note.folder = folder if folder else None - note.tags = ','.join(tag_list) if tag_list else None - - # Sync metadata from frontmatter if present - note.sync_from_frontmatter() - - # Update team_id if visibility is Team - if visibility == 'Team' and g.user.team_id: - note.team_id = g.user.team_id - else: - note.team_id = None - - # Update optional associations - if project_id: - project = Project.query.filter_by(id=project_id, company_id=g.user.company_id).first() - note.project_id = project.id if project else None - else: - note.project_id = None - - if task_id: - task = Task.query.filter_by(id=task_id).first() - if task and task.project.company_id == g.user.company_id: - note.task_id = task.id - else: - note.task_id = None - else: - note.task_id = None - - # Regenerate slug if title changed - new_slug = note.generate_slug() - if new_slug != note.slug: - note.slug = new_slug - - db.session.commit() - - flash('Note updated successfully', 'success') - return redirect(url_for('view_note', slug=note.slug)) - - except Exception as e: - db.session.rollback() - logger.error(f"Error updating note: {str(e)}") - flash('Error updating note', 'error') - return redirect(url_for('edit_note', slug=slug)) - - # GET request - show form - projects = Project.query.filter_by(company_id=g.user.company_id, is_active=True).all() - tasks = [] - if note.project_id: - tasks = Task.query.filter_by(project_id=note.project_id).all() - - # Get all existing folders for suggestions - all_folders = set() - - # Get folders from NoteFolder table - folder_records = NoteFolder.query.filter_by(company_id=g.user.company_id).all() - for folder in folder_records: - all_folders.add(folder.path) - - # Also get folders from notes (for backward compatibility) - folder_notes = Note.query.filter_by(company_id=g.user.company_id, is_archived=False).filter(Note.folder != None).all() - for n in folder_notes: - if n.folder: - all_folders.add(n.folder) - - return render_template('note_editor.html', - title=f'Edit: {note.title}', - note=note, - projects=projects, - tasks=tasks, - all_folders=sorted(list(all_folders)), - NoteVisibility=NoteVisibility) - - -@app.route('/notes//delete', methods=['POST']) -@login_required -@company_required -def delete_note(slug): - """Delete (archive) a note""" - note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() - - # Check permissions - if not note.can_user_edit(g.user): - abort(403) - - try: - # Soft delete - note.is_archived = True - note.archived_at = datetime.now() - db.session.commit() - - flash('Note deleted successfully', 'success') - return redirect(url_for('notes_list')) - - except Exception as e: - db.session.rollback() - logger.error(f"Error deleting note: {str(e)}") - flash('Error deleting note', 'error') - return redirect(url_for('view_note', slug=slug)) - - -@app.route('/notes/folders') -@login_required -@company_required -def notes_folders(): - """Manage note folders""" - # Get all folders from NoteFolder table - all_folders = set() - folder_records = NoteFolder.query.filter_by(company_id=g.user.company_id).all() - - for folder in folder_records: - all_folders.add(folder.path) - - # Also get folders from notes (for backward compatibility) - folder_notes = Note.query.filter_by(company_id=g.user.company_id, is_archived=False).filter(Note.folder != None).all() - - folder_counts = {} - for note in folder_notes: - if note.folder and note.can_user_view(g.user): - # Add this folder and all parent folders - parts = note.folder.split('/') - for i in range(len(parts)): - folder_path = '/'.join(parts[:i+1]) - all_folders.add(folder_path) - folder_counts[folder_path] = folder_counts.get(folder_path, 0) + (1 if i == len(parts)-1 else 0) - - # Initialize counts for empty folders - for folder_path in all_folders: - if folder_path not in folder_counts: - folder_counts[folder_path] = 0 - - # Build folder tree structure - folder_tree = {} - for folder in sorted(all_folders): - parts = folder.split('/') - current = folder_tree - - for i, part in enumerate(parts): - if i == len(parts) - 1: - # Leaf folder - current[folder] = {} - else: - # Navigate to parent - parent_path = '/'.join(parts[:i+1]) - if parent_path not in current: - current[parent_path] = {} - current = current[parent_path] - - return render_template('notes_folders.html', - title='Note Folders', - all_folders=sorted(list(all_folders)), - folder_tree=folder_tree, - folder_counts=folder_counts) - - -@app.route('/api/notes/folder-details') -@login_required -@company_required -def api_folder_details(): - """Get details about a specific folder""" - folder_path = request.args.get('path', '') - - if not folder_path: - return jsonify({'error': 'Folder path required'}), 400 - - # Get notes in this folder - notes = Note.query.filter_by( - company_id=g.user.company_id, - folder=folder_path, - is_archived=False - ).all() - - # Filter by visibility - visible_notes = [n for n in notes if n.can_user_view(g.user)] - - # Get subfolders - all_folders = set() - folder_notes = Note.query.filter_by(company_id=g.user.company_id, is_archived=False).filter( - Note.folder.like(f'{folder_path}/%') - ).all() - - for note in folder_notes: - if note.folder and note.can_user_view(g.user): - # Get immediate subfolder - subfolder = note.folder[len(folder_path)+1:] - if '/' in subfolder: - subfolder = subfolder.split('/')[0] - all_folders.add(subfolder) - - # Get recent notes (last 5) - recent_notes = sorted(visible_notes, key=lambda n: n.updated_at, reverse=True)[:5] - - return jsonify({ - 'name': folder_path.split('/')[-1], - 'path': folder_path, - 'note_count': len(visible_notes), - 'subfolder_count': len(all_folders), - 'recent_notes': [ - { - 'title': n.title, - 'slug': n.slug, - 'updated_at': n.updated_at.strftime('%Y-%m-%d %H:%M') - } for n in recent_notes - ] - }) - - -@app.route('/api/notes/folders', methods=['POST']) -@login_required -@company_required -def api_create_folder(): - """Create a new folder""" - data = request.get_json() - folder_name = data.get('name', '').strip() - parent_folder = data.get('parent', '').strip() - - if not folder_name: - return jsonify({'success': False, 'message': 'Folder name is required'}), 400 - - # Validate folder name (no special characters except dash and underscore) - if not re.match(r'^[a-zA-Z0-9_\- ]+$', folder_name): - return jsonify({'success': False, 'message': 'Folder name can only contain letters, numbers, spaces, dashes, and underscores'}), 400 - - # Create full path - full_path = f"{parent_folder}/{folder_name}" if parent_folder else folder_name - - # Check if folder already exists - existing_folder = NoteFolder.query.filter_by( - company_id=g.user.company_id, - path=full_path - ).first() - - if existing_folder: - return jsonify({'success': False, 'message': 'Folder already exists'}), 400 - - # Create the folder - try: - folder = NoteFolder( - name=folder_name, - path=full_path, - parent_path=parent_folder if parent_folder else None, - description=data.get('description', ''), - created_by_id=g.user.id, - company_id=g.user.company_id - ) - - db.session.add(folder) - db.session.commit() - - return jsonify({ - 'success': True, - 'message': 'Folder created successfully', - 'folder': { - 'name': folder_name, - 'path': full_path - } - }) - except Exception as e: - db.session.rollback() - logger.error(f"Error creating folder: {str(e)}") - return jsonify({'success': False, 'message': 'Error creating folder'}), 500 - - -@app.route('/api/notes/folders', methods=['PUT']) -@login_required -@company_required -def api_rename_folder(): - """Rename an existing folder""" - data = request.get_json() - old_path = data.get('old_path', '').strip() - new_name = data.get('new_name', '').strip() - - if not old_path or not new_name: - return jsonify({'success': False, 'message': 'Old path and new name are required'}), 400 - - # Validate folder name - if not re.match(r'^[a-zA-Z0-9_\- ]+$', new_name): - return jsonify({'success': False, 'message': 'Folder name can only contain letters, numbers, spaces, dashes, and underscores'}), 400 - - # Build new path - path_parts = old_path.split('/') - path_parts[-1] = new_name - new_path = '/'.join(path_parts) - - # Update all notes in this folder and subfolders - notes_to_update = Note.query.filter( - Note.company_id == g.user.company_id, - db.or_( - Note.folder == old_path, - Note.folder.like(f'{old_path}/%') - ) - ).all() - - # Check permissions for all notes - for note in notes_to_update: - if not note.can_user_edit(g.user): - return jsonify({'success': False, 'message': 'You do not have permission to modify all notes in this folder'}), 403 - - # Update folder paths - try: - for note in notes_to_update: - if note.folder == old_path: - note.folder = new_path - else: - # Update subfolder path - note.folder = new_path + note.folder[len(old_path):] - - db.session.commit() - - return jsonify({ - 'success': True, - 'message': f'Renamed folder to {new_name}', - 'updated_count': len(notes_to_update) - }) - except Exception as e: - db.session.rollback() - logger.error(f"Error renaming folder: {str(e)}") - return jsonify({'success': False, 'message': 'Error renaming folder'}), 500 - - -@app.route('/api/notes/folders', methods=['DELETE']) -@login_required -@company_required -def api_delete_folder(): - """Delete an empty folder""" - folder_path = request.args.get('path', '').strip() - - if not folder_path: - return jsonify({'success': False, 'message': 'Folder path is required'}), 400 - - # Check if folder has any notes - notes_in_folder = Note.query.filter_by( - company_id=g.user.company_id, - folder=folder_path, - is_archived=False - ).all() - - if notes_in_folder: - return jsonify({'success': False, 'message': 'Cannot delete folder that contains notes'}), 400 - - # Check if folder has subfolders with notes - notes_in_subfolders = Note.query.filter( - Note.company_id == g.user.company_id, - Note.folder.like(f'{folder_path}/%'), - Note.is_archived == False - ).first() - - if notes_in_subfolders: - return jsonify({'success': False, 'message': 'Cannot delete folder that contains subfolders with notes'}), 400 - - # Since we don't have a separate folders table, we just return success - # The folder will disappear from the UI when there are no notes in it - - return jsonify({ - 'success': True, - 'message': 'Folder deleted successfully' - }) - - -@app.route('/api/notes//folder', methods=['PUT']) -@login_required -@company_required -def update_note_folder(slug): - """Update a note's folder via drag and drop""" - note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() - - # Check permissions - if not note.can_user_edit(g.user): - return jsonify({'success': False, 'message': 'Permission denied'}), 403 - - data = request.get_json() - folder_path = data.get('folder', '').strip() - - try: - # Update the note's folder - note.folder = folder_path if folder_path else None - db.session.commit() - - return jsonify({ - 'success': True, - 'message': 'Note moved successfully', - 'folder': folder_path - }) - except Exception as e: - db.session.rollback() - logger.error(f"Error updating note folder: {str(e)}") - return jsonify({'success': False, 'message': 'Error updating note folder'}), 500 - - -@app.route('/api/notes//tags', methods=['POST']) -@login_required -@company_required -def add_tags_to_note(note_id): - """Add tags to a note""" - note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first_or_404() - - # Check permissions - if not note.can_user_edit(g.user): - return jsonify({'success': False, 'message': 'Permission denied'}), 403 - - data = request.get_json() - new_tags = data.get('tags', '').strip() - - if not new_tags: - return jsonify({'success': False, 'message': 'No tags provided'}), 400 - - try: - # Get existing tags - existing_tags = note.get_tags_list() - - # Parse new tags - new_tag_list = [tag.strip() for tag in new_tags.split(',') if tag.strip()] - - # Merge tags (avoid duplicates) - all_tags = list(set(existing_tags + new_tag_list)) - - # Update note - note.set_tags_list(all_tags) - db.session.commit() - - return jsonify({ - 'success': True, - 'message': 'Tags added successfully', - 'tags': note.tags - }) - except Exception as e: - db.session.rollback() - logger.error(f"Error adding tags to note: {str(e)}") - return jsonify({'success': False, 'message': 'Error adding tags'}), 500 - - -@app.route('/api/notes//link', methods=['POST']) -@login_required -@company_required -def link_notes(note_id): - """Create a link between two notes""" - source_note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first_or_404() - - # Check permissions - if not source_note.can_user_edit(g.user): - return jsonify({'success': False, 'message': 'Permission denied'}), 403 - - data = request.get_json() - target_note_id = data.get('target_note_id') - link_type = data.get('link_type', 'related') - - if not target_note_id: - return jsonify({'success': False, 'message': 'Target note ID required'}), 400 - - target_note = Note.query.filter_by(id=target_note_id, company_id=g.user.company_id).first() - if not target_note: - return jsonify({'success': False, 'message': 'Target note not found'}), 404 - - if not target_note.can_user_view(g.user): - return jsonify({'success': False, 'message': 'Cannot link to this note'}), 403 - - try: - # Check if link already exists (in either direction) - existing_link = NoteLink.query.filter( - db.or_( - db.and_( - NoteLink.source_note_id == note_id, - NoteLink.target_note_id == target_note_id - ), - db.and_( - NoteLink.source_note_id == target_note_id, - NoteLink.target_note_id == note_id - ) - ) - ).first() - - if existing_link: - return jsonify({'success': False, 'message': 'Link already exists between these notes'}), 400 - - # Create link - link = NoteLink( - source_note_id=note_id, - target_note_id=target_note_id, - link_type=link_type, - created_by_id=g.user.id - ) - - db.session.add(link) - db.session.commit() - - return jsonify({ - 'success': True, - 'message': 'Notes linked successfully', - 'link': { - 'id': link.id, - 'source_note_id': link.source_note_id, - 'target_note_id': link.target_note_id, - 'target_note': { - 'id': target_note.id, - 'title': target_note.title, - 'slug': target_note.slug - } - } - }) - - except Exception as e: - db.session.rollback() - logger.error(f"Error linking notes: {str(e)}") - return jsonify({'success': False, 'message': f'Error linking notes: {str(e)}'}), 500 - -# We can keep this route as a redirect to home for backward compatibility @app.route('/timetrack') @login_required def timetrack(): @@ -4991,7 +3870,6 @@ def manage_project_tasks(project_id): team_members=team_members) - # Unified Task Management Route @app.route('/tasks') @role_required(Role.TEAM_MEMBER) @@ -7073,50 +5951,6 @@ def render_markdown(): return jsonify({'html': '

    Error rendering markdown

    '}) # Note link deletion endpoint -@app.route('/api/notes//link', methods=['DELETE']) -@login_required -@company_required -def unlink_notes(note_id): - """Remove a link between two notes""" - try: - note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first() - - if not note: - return jsonify({'success': False, 'message': 'Note not found'}) - - if not note.can_user_edit(g.user): - return jsonify({'success': False, 'message': 'Permission denied'}) - - data = request.get_json() - target_note_id = data.get('target_note_id') - - if not target_note_id: - return jsonify({'success': False, 'message': 'Target note ID required'}) - - # Find and remove the link - link = NoteLink.query.filter_by( - source_note_id=note_id, - target_note_id=target_note_id - ).first() - - if not link: - # Try reverse direction - link = NoteLink.query.filter_by( - source_note_id=target_note_id, - target_note_id=note_id - ).first() - - if link: - db.session.delete(link) - db.session.commit() - return jsonify({'success': True, 'message': 'Link removed successfully'}) - else: - return jsonify({'success': False, 'message': 'Link not found'}) - - except Exception as e: - db.session.rollback() - logger.error(f"Error removing note link: {str(e)}") - return jsonify({'success': False, 'message': str(e)}) if __name__ == '__main__': port = int(os.environ.get('PORT', 5000)) diff --git a/routes/__init__.py b/routes/__init__.py new file mode 100644 index 0000000..8ca12ba --- /dev/null +++ b/routes/__init__.py @@ -0,0 +1 @@ +# Routes package initialization \ No newline at end of file diff --git a/routes/auth.py b/routes/auth.py new file mode 100644 index 0000000..a7b09e9 --- /dev/null +++ b/routes/auth.py @@ -0,0 +1,76 @@ +# Standard library imports +from functools import wraps + +# Third-party imports +from flask import flash, g, redirect, request, url_for + +# Local application imports +from models import Company, Role, User + + +def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user is None: + return redirect(url_for('login', next=request.url)) + return f(*args, **kwargs) + return decorated_function + + +def company_required(f): + """ + Decorator to ensure user has a valid company association and set company context. + """ + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user is None: + return redirect(url_for('login', next=request.url)) + + # System admins can access without company association + if g.user.role == Role.SYSTEM_ADMIN: + return f(*args, **kwargs) + + if g.user.company_id is None: + flash('You must be associated with a company to access this page.', 'error') + return redirect(url_for('setup_company')) + + # Set company context + g.company = Company.query.get(g.user.company_id) + if not g.company or not g.company.is_active: + flash('Your company account is inactive.', 'error') + return redirect(url_for('home')) + + return f(*args, **kwargs) + return decorated_function + + +def role_required(*allowed_roles): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user.role not in allowed_roles: + flash('You do not have permission to access this page.', 'error') + return redirect(url_for('dashboard')) + return f(*args, **kwargs) + return decorated_function + return decorator + + +def admin_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user.role not in [Role.ADMIN, Role.SYSTEM_ADMIN]: + flash('Admin access required.', 'error') + return redirect(url_for('dashboard')) + return f(*args, **kwargs) + return decorated_function + + +def system_admin_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user.role != Role.SYSTEM_ADMIN: + flash('System admin access required.', 'error') + return redirect(url_for('dashboard')) + return f(*args, **kwargs) + return decorated_function \ No newline at end of file diff --git a/routes/notes.py b/routes/notes.py new file mode 100644 index 0000000..b4e7e3e --- /dev/null +++ b/routes/notes.py @@ -0,0 +1,491 @@ +# Standard library imports +from datetime import datetime, timezone + +# Third-party imports +from flask import (Blueprint, abort, flash, g, jsonify, redirect, + render_template, request, url_for) +from sqlalchemy import and_, or_ + +# Local application imports +from models import (Note, NoteFolder, NoteLink, NoteVisibility, Project, + Task, db) +from routes.auth import company_required, login_required + +# Create blueprint +notes_bp = Blueprint('notes', __name__, url_prefix='/notes') + + +@notes_bp.route('') +@login_required +@company_required +def notes_list(): + """List all notes with optional filtering""" + import logging + logger = logging.getLogger(__name__) + logger.info("Notes list route called") + + # Get filter parameters + folder_filter = request.args.get('folder', '') + tag_filter = request.args.get('tag', '') + visibility_filter = request.args.get('visibility', '') + search_query = request.args.get('search', '') + + # Base query - only non-archived notes for the user's company + query = Note.query.filter_by( + company_id=g.user.company_id, + is_archived=False + ) + + # Apply folder filter + if folder_filter: + query = query.filter_by(folder=folder_filter) + + # Apply tag filter + if tag_filter: + query = query.filter(Note.tags.contains(tag_filter)) + + # Apply visibility filter + if visibility_filter: + if visibility_filter == 'private': + query = query.filter_by(visibility=NoteVisibility.PRIVATE, created_by_id=g.user.id) + elif visibility_filter == 'team': + query = query.filter( + and_( + Note.visibility == NoteVisibility.TEAM, + or_( + Note.created_by.has(team_id=g.user.team_id), + Note.created_by_id == g.user.id + ) + ) + ) + elif visibility_filter == 'company': + query = query.filter_by(visibility=NoteVisibility.COMPANY) + else: + # Default visibility filtering - show notes user can see + query = query.filter( + or_( + # Private notes created by user + and_(Note.visibility == NoteVisibility.PRIVATE, Note.created_by_id == g.user.id), + # Team notes from user's team + and_(Note.visibility == NoteVisibility.TEAM, Note.created_by.has(team_id=g.user.team_id)), + # Company notes + Note.visibility == NoteVisibility.COMPANY + ) + ) + + # Apply search filter + if search_query: + search_pattern = f'%{search_query}%' + query = query.filter( + or_( + Note.title.ilike(search_pattern), + Note.content.ilike(search_pattern), + Note.tags.ilike(search_pattern) + ) + ) + + # Order by pinned first, then by updated date + notes = query.order_by(Note.is_pinned.desc(), Note.updated_at.desc()).all() + + # Get all folders for the sidebar + all_folders = NoteFolder.query.filter_by( + company_id=g.user.company_id + ).order_by(NoteFolder.path).all() + + # Build folder tree structure + folder_tree = {} + folder_counts = {} + + # Count notes per folder + folder_note_counts = db.session.query( + Note.folder, db.func.count(Note.id) + ).filter_by( + company_id=g.user.company_id, + is_archived=False + ).group_by(Note.folder).all() + + for folder, count in folder_note_counts: + if folder: + folder_counts[folder] = count + + # Build folder tree structure + for folder in all_folders: + parts = folder.path.split('/') + current = folder_tree + + for i, part in enumerate(parts): + if i == len(parts) - 1: + # Leaf folder - use full path as key + current[folder.path] = {} + else: + # Navigate to parent using full path + parent_path = '/'.join(parts[:i+1]) + if parent_path not in current: + current[parent_path] = {} + current = current[parent_path] + + # Get all unique tags + all_notes_for_tags = Note.query.filter_by( + company_id=g.user.company_id, + is_archived=False + ).all() + + all_tags = set() + tag_counts = {} + + for note in all_notes_for_tags: + if note.tags: + note_tags = note.get_tags_list() + for tag in note_tags: + all_tags.add(tag) + tag_counts[tag] = tag_counts.get(tag, 0) + 1 + + all_tags = sorted(list(all_tags)) + + # Count notes by visibility + visibility_counts = { + 'private': Note.query.filter_by( + company_id=g.user.company_id, + visibility=NoteVisibility.PRIVATE, + created_by_id=g.user.id, + is_archived=False + ).count(), + 'team': Note.query.filter( + Note.company_id == g.user.company_id, + Note.visibility == NoteVisibility.TEAM, + Note.created_by.has(team_id=g.user.team_id), + Note.is_archived == False + ).count(), + 'company': Note.query.filter_by( + company_id=g.user.company_id, + visibility=NoteVisibility.COMPANY, + is_archived=False + ).count() + } + + try: + logger.info(f"Rendering template with {len(notes)} notes, folder_tree type: {type(folder_tree)}") + return render_template('notes_list.html', + notes=notes, + folder_tree=folder_tree, + folder_counts=folder_counts, + all_tags=all_tags, + tag_counts=tag_counts, + visibility_counts=visibility_counts, + folder_filter=folder_filter, + tag_filter=tag_filter, + visibility_filter=visibility_filter, + search_query=search_query, + title='Notes') + except Exception as e: + logger.error(f"Error rendering notes template: {str(e)}", exc_info=True) + raise + + +@notes_bp.route('/new', methods=['GET', 'POST']) +@login_required +@company_required +def create_note(): + """Create a new note""" + if request.method == 'POST': + title = request.form.get('title', '').strip() + content = request.form.get('content', '').strip() + visibility = request.form.get('visibility', 'Private') + folder = request.form.get('folder', '').strip() + tags = request.form.get('tags', '').strip() + project_id = request.form.get('project_id') + task_id = request.form.get('task_id') + + # Validate + if not title: + flash('Title is required', 'error') + return redirect(url_for('notes.create_note')) + + if not content: + flash('Content is required', 'error') + return redirect(url_for('notes.create_note')) + + # Validate visibility + try: + visibility_enum = NoteVisibility[visibility.upper()] + except KeyError: + visibility_enum = NoteVisibility.PRIVATE + + # Validate project if provided + project = None + if project_id: + project = Project.query.filter_by( + id=project_id, + company_id=g.user.company_id + ).first() + if not project: + flash('Invalid project selected', 'error') + return redirect(url_for('notes.create_note')) + + # Validate task if provided + task = None + if task_id: + task = Task.query.filter_by(id=task_id).first() + if not task or (project and task.project_id != project.id): + flash('Invalid task selected', 'error') + return redirect(url_for('notes.create_note')) + + # Create note + note = Note( + title=title, + content=content, + visibility=visibility_enum, + folder=folder if folder else None, + tags=tags if tags else None, + company_id=g.user.company_id, + created_by_id=g.user.id, + project_id=project.id if project else None, + task_id=task.id if task else None + ) + + db.session.add(note) + db.session.commit() + + flash('Note created successfully', 'success') + return redirect(url_for('notes.view_note', slug=note.slug)) + + # GET request - show form + # Get folders for dropdown + folders = NoteFolder.query.filter_by( + company_id=g.user.company_id + ).order_by(NoteFolder.path).all() + + # Get projects for dropdown + projects = Project.query.filter_by( + company_id=g.user.company_id, + is_archived=False + ).order_by(Project.name).all() + + # Get task if specified in URL + task_id = request.args.get('task_id') + task = None + if task_id: + task = Task.query.filter_by(id=task_id).first() + + return render_template('note_editor.html', + folders=folders, + projects=projects, + task=task, + title='Create Note') + + +@notes_bp.route('/') +@login_required +@company_required +def view_note(slug): + """View a note""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_view(g.user): + abort(403) + + # Get linked notes + outgoing_links = NoteLink.query.filter_by( + source_note_id=note.id + ).join( + Note, NoteLink.target_note_id == Note.id + ).filter( + Note.is_archived == False + ).all() + + incoming_links = NoteLink.query.filter_by( + target_note_id=note.id + ).join( + Note, NoteLink.source_note_id == Note.id + ).filter( + Note.is_archived == False + ).all() + + # Get linkable notes for the modal + linkable_notes = Note.query.filter( + Note.company_id == g.user.company_id, + Note.id != note.id, + Note.is_archived == False + ).filter( + or_( + # User's private notes + and_(Note.visibility == NoteVisibility.PRIVATE, Note.created_by_id == g.user.id), + # Team notes + and_(Note.visibility == NoteVisibility.TEAM, Note.created_by.has(team_id=g.user.team_id)), + # Company notes + Note.visibility == NoteVisibility.COMPANY + ) + ).order_by(Note.title).all() + + return render_template('note_view.html', + note=note, + outgoing_links=outgoing_links, + incoming_links=incoming_links, + linkable_notes=linkable_notes, + title=note.title) + + +@notes_bp.route('//mindmap') +@login_required +@company_required +def view_note_mindmap(slug): + """View a note as a mind map""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_view(g.user): + abort(403) + + return render_template('note_mindmap.html', note=note, title=f"{note.title} - Mind Map") + + +@notes_bp.route('//edit', methods=['GET', 'POST']) +@login_required +@company_required +def edit_note(slug): + """Edit an existing note""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_edit(g.user): + abort(403) + + if request.method == 'POST': + title = request.form.get('title', '').strip() + content = request.form.get('content', '').strip() + visibility = request.form.get('visibility', 'Private') + folder = request.form.get('folder', '').strip() + tags = request.form.get('tags', '').strip() + project_id = request.form.get('project_id') + task_id = request.form.get('task_id') + + # Validate + if not title: + flash('Title is required', 'error') + return redirect(url_for('notes.edit_note', slug=slug)) + + if not content: + flash('Content is required', 'error') + return redirect(url_for('notes.edit_note', slug=slug)) + + # Validate visibility + try: + visibility_enum = NoteVisibility[visibility.upper()] + except KeyError: + visibility_enum = NoteVisibility.PRIVATE + + # Validate project if provided + project = None + if project_id: + project = Project.query.filter_by( + id=project_id, + company_id=g.user.company_id + ).first() + if not project: + flash('Invalid project selected', 'error') + return redirect(url_for('notes.edit_note', slug=slug)) + + # Validate task if provided + task = None + if task_id: + task = Task.query.filter_by(id=task_id).first() + if not task or (project and task.project_id != project.id): + flash('Invalid task selected', 'error') + return redirect(url_for('notes.edit_note', slug=slug)) + + # Update note + note.title = title + note.content = content + note.visibility = visibility_enum + note.folder = folder if folder else None + note.tags = tags if tags else None + note.project_id = project.id if project else None + note.task_id = task.id if task else None + note.updated_at = datetime.now(timezone.utc) + + # Update slug if title changed + note.generate_slug() + + db.session.commit() + + flash('Note updated successfully', 'success') + return redirect(url_for('notes.view_note', slug=note.slug)) + + # GET request - show form + # Get folders for dropdown + folders = NoteFolder.query.filter_by( + company_id=g.user.company_id + ).order_by(NoteFolder.path).all() + + # Get projects for dropdown + projects = Project.query.filter_by( + company_id=g.user.company_id, + is_archived=False + ).order_by(Project.name).all() + + return render_template('note_editor.html', + note=note, + folders=folders, + projects=projects, + title=f'Edit {note.title}') + + +@notes_bp.route('//delete', methods=['POST']) +@login_required +@company_required +def delete_note(slug): + """Delete (archive) a note""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_edit(g.user): + abort(403) + + # Archive the note + note.is_archived = True + note.updated_at = datetime.utcnow() + + db.session.commit() + + flash('Note deleted successfully', 'success') + return redirect(url_for('notes.notes_list')) + + +@notes_bp.route('/folders') +@login_required +@company_required +def notes_folders(): + """Manage note folders""" + # Get all folders + folders = NoteFolder.query.filter_by( + company_id=g.user.company_id + ).order_by(NoteFolder.path).all() + + # Get note counts per folder + folder_counts = {} + folder_note_counts = db.session.query( + Note.folder, db.func.count(Note.id) + ).filter_by( + company_id=g.user.company_id, + is_archived=False + ).filter(Note.folder.isnot(None)).group_by(Note.folder).all() + + for folder, count in folder_note_counts: + folder_counts[folder] = count + + # Build folder tree + folder_tree = {} + for folder in folders: + parts = folder.path.split('/') + current = folder_tree + for part in parts: + if part not in current: + current[part] = {} + current = current[part] + + return render_template('notes_folders.html', + folders=folders, + folder_tree=folder_tree, + folder_counts=folder_counts, + title='Manage Folders') \ No newline at end of file diff --git a/routes/notes_api.py b/routes/notes_api.py new file mode 100644 index 0000000..a487ec7 --- /dev/null +++ b/routes/notes_api.py @@ -0,0 +1,388 @@ +# Standard library imports +from datetime import datetime, timezone + +# Third-party imports +from flask import Blueprint, abort, g, jsonify, request +from sqlalchemy import and_, or_ + +# Local application imports +from models import Note, NoteFolder, NoteLink, NoteVisibility, db +from routes.auth import company_required, login_required + +# Create blueprint +notes_api_bp = Blueprint('notes_api', __name__, url_prefix='/api/notes') + + +@notes_api_bp.route('/folder-details') +@login_required +@company_required +def api_folder_details(): + """Get folder details including note count""" + folder_path = request.args.get('folder', '') + + # Get note count for this folder + note_count = Note.query.filter_by( + company_id=g.user.company_id, + folder=folder_path, + is_archived=False + ).count() + + # Check if folder exists in NoteFolder table + folder = NoteFolder.query.filter_by( + company_id=g.user.company_id, + path=folder_path + ).first() + + return jsonify({ + 'success': True, + 'folder': { + 'path': folder_path, + 'name': folder_path.split('/')[-1] if folder_path else 'Root', + 'exists': folder is not None, + 'note_count': note_count, + 'created_at': folder.created_at.isoformat() if folder else None + } + }) + + +@notes_api_bp.route('/folders', methods=['POST']) +@login_required +@company_required +def api_create_folder(): + """Create a new folder""" + data = request.get_json() + if not data: + return jsonify({'success': False, 'message': 'No data provided'}), 400 + + folder_name = data.get('name', '').strip() + parent_path = data.get('parent', '').strip() + + if not folder_name: + return jsonify({'success': False, 'message': 'Folder name is required'}), 400 + + # Validate folder name + if '/' in folder_name: + return jsonify({'success': False, 'message': 'Folder name cannot contain /'}), 400 + + # Build full path + if parent_path: + full_path = f"{parent_path}/{folder_name}" + else: + full_path = folder_name + + # Check if folder already exists + existing = NoteFolder.query.filter_by( + company_id=g.user.company_id, + path=full_path + ).first() + + if existing: + return jsonify({'success': False, 'message': 'Folder already exists'}), 400 + + # Create folder + folder = NoteFolder( + path=full_path, + company_id=g.user.company_id, + created_by_id=g.user.id + ) + + db.session.add(folder) + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Folder created successfully', + 'folder': { + 'path': folder.path, + 'name': folder_name + } + }) + + +@notes_api_bp.route('/folders', methods=['PUT']) +@login_required +@company_required +def api_rename_folder(): + """Rename a folder""" + data = request.get_json() + if not data: + return jsonify({'success': False, 'message': 'No data provided'}), 400 + + old_path = data.get('old_path', '').strip() + new_name = data.get('new_name', '').strip() + + if not old_path or not new_name: + return jsonify({'success': False, 'message': 'Both old path and new name are required'}), 400 + + # Validate new name + if '/' in new_name: + return jsonify({'success': False, 'message': 'Folder name cannot contain /'}), 400 + + # Find the folder + folder = NoteFolder.query.filter_by( + company_id=g.user.company_id, + path=old_path + ).first() + + if not folder: + return jsonify({'success': False, 'message': 'Folder not found'}), 404 + + # Build new path + path_parts = old_path.split('/') + path_parts[-1] = new_name + new_path = '/'.join(path_parts) + + # Check if new path already exists + existing = NoteFolder.query.filter_by( + company_id=g.user.company_id, + path=new_path + ).first() + + if existing: + return jsonify({'success': False, 'message': 'A folder with this name already exists'}), 400 + + # Update folder path + folder.path = new_path + + # Update all notes in this folder + Note.query.filter_by( + company_id=g.user.company_id, + folder=old_path + ).update({Note.folder: new_path}) + + # Update all subfolders + subfolders = NoteFolder.query.filter( + NoteFolder.company_id == g.user.company_id, + NoteFolder.path.like(f"{old_path}/%") + ).all() + + for subfolder in subfolders: + subfolder.path = subfolder.path.replace(old_path, new_path, 1) + + # Update all notes in subfolders + notes_in_subfolders = Note.query.filter( + Note.company_id == g.user.company_id, + Note.folder.like(f"{old_path}/%") + ).all() + + for note in notes_in_subfolders: + note.folder = note.folder.replace(old_path, new_path, 1) + + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Folder renamed successfully', + 'folder': { + 'old_path': old_path, + 'new_path': new_path + } + }) + + +@notes_api_bp.route('/folders', methods=['DELETE']) +@login_required +@company_required +def api_delete_folder(): + """Delete a folder""" + folder_path = request.args.get('path', '').strip() + + if not folder_path: + return jsonify({'success': False, 'message': 'Folder path is required'}), 400 + + # Check if folder has notes + note_count = Note.query.filter_by( + company_id=g.user.company_id, + folder=folder_path, + is_archived=False + ).count() + + if note_count > 0: + return jsonify({ + 'success': False, + 'message': f'Cannot delete folder with {note_count} notes. Please move or delete the notes first.' + }), 400 + + # Find and delete the folder + folder = NoteFolder.query.filter_by( + company_id=g.user.company_id, + path=folder_path + ).first() + + if folder: + db.session.delete(folder) + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Folder deleted successfully' + }) + + +@notes_api_bp.route('//folder', methods=['PUT']) +@login_required +@company_required +def update_note_folder(slug): + """Update a note's folder via drag and drop""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_edit(g.user): + return jsonify({'success': False, 'message': 'Permission denied'}), 403 + + data = request.get_json() + new_folder = data.get('folder', '').strip() + + # Update note folder + note.folder = new_folder if new_folder else None + note.updated_at = datetime.now(timezone.utc) + + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Note moved successfully' + }) + + +@notes_api_bp.route('//tags', methods=['POST']) +@login_required +@company_required +def add_tags_to_note(note_id): + """Add tags to a note""" + note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first() + + if not note: + return jsonify({'success': False, 'message': 'Note not found'}), 404 + + # Check permissions + if not note.can_user_edit(g.user): + return jsonify({'success': False, 'message': 'Permission denied'}), 403 + + data = request.get_json() + new_tags = data.get('tags', '').strip() + + if not new_tags: + return jsonify({'success': False, 'message': 'No tags provided'}), 400 + + # Merge with existing tags + existing_tags = note.get_tags_list() + new_tag_list = [tag.strip() for tag in new_tags.split(',') if tag.strip()] + + # Combine and deduplicate + all_tags = list(set(existing_tags + new_tag_list)) + + # Update note + note.tags = ', '.join(sorted(all_tags)) + note.updated_at = datetime.now(timezone.utc) + + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Tags added successfully', + 'tags': all_tags + }) + + +@notes_api_bp.route('//link', methods=['POST']) +@login_required +@company_required +def link_notes(note_id): + """Create a link between two notes""" + source_note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first() + + if not source_note: + return jsonify({'success': False, 'message': 'Source note not found'}), 404 + + # Check permissions + if not source_note.can_user_edit(g.user): + return jsonify({'success': False, 'message': 'Permission denied'}), 403 + + data = request.get_json() + target_note_id = data.get('target_note_id') + link_type = data.get('link_type', 'related') + + if not target_note_id: + return jsonify({'success': False, 'message': 'Target note ID is required'}), 400 + + # Get target note + target_note = Note.query.filter_by(id=target_note_id, company_id=g.user.company_id).first() + + if not target_note: + return jsonify({'success': False, 'message': 'Target note not found'}), 404 + + # Check if user can view target note + if not target_note.can_user_view(g.user): + return jsonify({'success': False, 'message': 'You cannot link to a note you cannot view'}), 403 + + # Check if link already exists + existing_link = NoteLink.query.filter_by( + source_note_id=source_note.id, + target_note_id=target_note.id + ).first() + + if existing_link: + return jsonify({'success': False, 'message': 'Link already exists'}), 400 + + # Create link + link = NoteLink( + source_note_id=source_note.id, + target_note_id=target_note.id, + link_type=link_type, + created_by_id=g.user.id + ) + + db.session.add(link) + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Notes linked successfully' + }) + + +@notes_api_bp.route('//link', methods=['DELETE']) +@login_required +@company_required +def unlink_notes(note_id): + """Remove a link between two notes""" + source_note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first() + + if not source_note: + return jsonify({'success': False, 'message': 'Source note not found'}), 404 + + # Check permissions + if not source_note.can_user_edit(g.user): + return jsonify({'success': False, 'message': 'Permission denied'}), 403 + + data = request.get_json() + target_note_id = data.get('target_note_id') + + if not target_note_id: + return jsonify({'success': False, 'message': 'Target note ID is required'}), 400 + + # Find and delete the link (check both directions) + link = NoteLink.query.filter( + or_( + and_( + NoteLink.source_note_id == source_note.id, + NoteLink.target_note_id == target_note_id + ), + and_( + NoteLink.source_note_id == target_note_id, + NoteLink.target_note_id == source_note.id + ) + ) + ).first() + + if not link: + return jsonify({'success': False, 'message': 'Link not found'}), 404 + + db.session.delete(link) + db.session.commit() + + return jsonify({ + 'success': True, + 'message': 'Link removed successfully' + }) \ No newline at end of file diff --git a/routes/notes_download.py b/routes/notes_download.py new file mode 100644 index 0000000..ac35e46 --- /dev/null +++ b/routes/notes_download.py @@ -0,0 +1,288 @@ +# Standard library imports +import os +import re +import tempfile +import zipfile +from datetime import datetime +from urllib.parse import unquote + +# Third-party imports +from flask import (Blueprint, Response, abort, flash, g, redirect, request, + send_file, url_for) + +# Local application imports +from frontmatter_utils import parse_frontmatter +from models import Note, db +from routes.auth import company_required, login_required + +# Create blueprint +notes_download_bp = Blueprint('notes_download', __name__) + + +@notes_download_bp.route('/notes//download/') +@login_required +@company_required +def download_note(slug, format): + """Download a note in various formats""" + note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404() + + # Check permissions + if not note.can_user_view(g.user): + abort(403) + + # Prepare filename + safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', note.title) + timestamp = datetime.now().strftime('%Y%m%d') + + if format == 'md': + # Download as Markdown with frontmatter + content = note.content + response = Response(content, mimetype='text/markdown') + response.headers['Content-Disposition'] = f'attachment; filename="{safe_filename}_{timestamp}.md"' + return response + + elif format == 'html': + # Download as HTML + html_content = f""" + + + + {note.title} + + + + + {note.render_html()} + +""" + response = Response(html_content, mimetype='text/html') + response.headers['Content-Disposition'] = f'attachment; filename="{safe_filename}_{timestamp}.html"' + return response + + elif format == 'txt': + # Download as plain text + metadata, body = parse_frontmatter(note.content) + + # Create plain text version + text_content = f"{note.title}\n{'=' * len(note.title)}\n\n" + text_content += f"Author: {note.created_by.username}\n" + text_content += f"Created: {note.created_at.strftime('%Y-%m-%d %H:%M')}\n" + text_content += f"Updated: {note.updated_at.strftime('%Y-%m-%d %H:%M')}\n" + text_content += f"Visibility: {note.visibility.value}\n" + if note.folder: + text_content += f"Folder: {note.folder}\n" + if note.tags: + text_content += f"Tags: {note.tags}\n" + text_content += "\n" + "-" * 40 + "\n\n" + + # Remove markdown formatting + text_body = body + # Remove headers markdown + text_body = re.sub(r'^#+\s+', '', text_body, flags=re.MULTILINE) + # Remove emphasis + text_body = re.sub(r'\*{1,2}([^\*]+)\*{1,2}', r'\1', text_body) + text_body = re.sub(r'_{1,2}([^_]+)_{1,2}', r'\1', text_body) + # Remove links but keep text + text_body = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text_body) + # Remove images + text_body = re.sub(r'!\[([^\]]*)\]\([^\)]+\)', r'[Image: \1]', text_body) + # Remove code blocks markers + text_body = re.sub(r'```[^`]*```', lambda m: m.group(0).replace('```', ''), text_body, flags=re.DOTALL) + text_body = re.sub(r'`([^`]+)`', r'\1', text_body) + + text_content += text_body + + response = Response(text_content, mimetype='text/plain') + response.headers['Content-Disposition'] = f'attachment; filename="{safe_filename}_{timestamp}.txt"' + return response + + else: + abort(404) + + +@notes_download_bp.route('/notes/download-bulk', methods=['POST']) +@login_required +@company_required +def download_notes_bulk(): + """Download multiple notes as a zip file""" + note_ids = request.form.getlist('note_ids[]') + format = request.form.get('format', 'md') + + if not note_ids: + flash('No notes selected for download', 'error') + return redirect(url_for('notes.notes_list')) + + # Create a temporary file for the zip + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip') + + try: + with zipfile.ZipFile(temp_file.name, 'w') as zipf: + for note_id in note_ids: + note = Note.query.filter_by(id=int(note_id), company_id=g.user.company_id).first() + if note and note.can_user_view(g.user): + # Get content based on format + safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', note.title) + + if format == 'md': + content = note.content + filename = f"{safe_filename}.md" + elif format == 'html': + content = f""" + + + + {note.title} + + + +

    {note.title}

    + {note.render_html()} + +""" + filename = f"{safe_filename}.html" + else: # txt + metadata, body = parse_frontmatter(note.content) + content = f"{note.title}\n{'=' * len(note.title)}\n\n{body}" + filename = f"{safe_filename}.txt" + + # Add file to zip + zipf.writestr(filename, content) + + # Send the zip file + temp_file.seek(0) + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + + return send_file( + temp_file.name, + mimetype='application/zip', + as_attachment=True, + download_name=f'notes_{timestamp}.zip' + ) + + finally: + # Clean up temp file after sending + os.unlink(temp_file.name) + + +@notes_download_bp.route('/notes/folder//download/') +@login_required +@company_required +def download_folder(folder_path, format): + """Download all notes in a folder as a zip file""" + # Decode folder path (replace URL encoding) + folder_path = unquote(folder_path) + + # Get all notes in this folder + notes = Note.query.filter_by( + company_id=g.user.company_id, + folder=folder_path, + is_archived=False + ).all() + + # Filter notes user can view + viewable_notes = [note for note in notes if note.can_user_view(g.user)] + + if not viewable_notes: + flash('No notes found in this folder or you don\'t have permission to view them.', 'warning') + return redirect(url_for('notes.notes_list')) + + # Create a temporary file for the zip + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip') + + try: + with zipfile.ZipFile(temp_file.name, 'w') as zipf: + for note in viewable_notes: + # Get content based on format + safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', note.title) + + if format == 'md': + content = note.content + filename = f"{safe_filename}.md" + elif format == 'html': + content = f""" + + + + {note.title} + + + + + {note.render_html()} + +""" + filename = f"{safe_filename}.html" + else: # txt + metadata, body = parse_frontmatter(note.content) + # Remove markdown formatting + text_body = body + text_body = re.sub(r'^#+\s+', '', text_body, flags=re.MULTILINE) + text_body = re.sub(r'\*{1,2}([^\*]+)\*{1,2}', r'\1', text_body) + text_body = re.sub(r'_{1,2}([^_]+)_{1,2}', r'\1', text_body) + text_body = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text_body) + text_body = re.sub(r'!\[([^\]]*)\]\([^\)]+\)', r'[Image: \1]', text_body) + text_body = re.sub(r'```[^`]*```', lambda m: m.group(0).replace('```', ''), text_body, flags=re.DOTALL) + text_body = re.sub(r'`([^`]+)`', r'\1', text_body) + + content = f"{note.title}\n{'=' * len(note.title)}\n\n" + content += f"Author: {note.created_by.username}\n" + content += f"Created: {note.created_at.strftime('%Y-%m-%d %H:%M')}\n" + content += f"Folder: {note.folder}\n\n" + content += "-" * 40 + "\n\n" + content += text_body + filename = f"{safe_filename}.txt" + + # Add file to zip + zipf.writestr(filename, content) + + # Send the zip file + temp_file.seek(0) + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + safe_folder_name = re.sub(r'[^a-zA-Z0-9_-]', '_', folder_path.replace('/', '_')) + + return send_file( + temp_file.name, + mimetype='application/zip', + as_attachment=True, + download_name=f'{safe_folder_name}_notes_{timestamp}.zip' + ) + + finally: + # Clean up temp file after sending + os.unlink(temp_file.name) \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html index e6d13d5..f55645e 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -132,7 +132,7 @@

    {{ g.user.username }}

  • 📊Dashboard
  • 📋Task Management
  • 🏃‍♂️Sprints
  • -
  • 📝Notes
  • +
  • 📝Notes
  • 📊Analytics
  • diff --git a/templates/note_editor.html b/templates/note_editor.html index 6b02cec..ab41aa6 100644 --- a/templates/note_editor.html +++ b/templates/note_editor.html @@ -145,7 +145,7 @@

    Note Settings

    - Cancel + Cancel @@ -170,7 +170,7 @@

    Linked Notes

    {% for link in note.linked_notes %}
    - + {{ link.target_note.title }} {{ link.link_type }} diff --git a/templates/note_mindmap.html b/templates/note_mindmap.html index 7ffbc3d..b8a2863 100644 --- a/templates/note_mindmap.html +++ b/templates/note_mindmap.html @@ -11,7 +11,7 @@

    {{ note.title }} - Mind Map View

    - + Back to Note
    diff --git a/templates/note_view.html b/templates/note_view.html index 040e44d..3be020a 100644 --- a/templates/note_view.html +++ b/templates/note_view.html @@ -31,21 +31,21 @@

    {{ note.title }}

    Download
    - + @@ -66,13 +66,13 @@

    {{ note.title }}

    Mind Map
    {% if note.can_user_edit(g.user) %} - Edit - Edit + {% endif %} - Back to Notes + Back to Notes @@ -104,7 +104,7 @@

    {{ note.title }}

    Tags: {% for tag in note.get_tags_list() %} - {{ tag }} + {{ tag }} {% endfor %} @@ -129,7 +129,7 @@

    Linked Notes

    {% for link in outgoing_links %}
    @@ -143,7 +143,7 @@

    {{ link.tar {% for link in incoming_links %}
    diff --git a/templates/notes_folders.html b/templates/notes_folders.html index 187c63b..fdb379e 100644 --- a/templates/notes_folders.html +++ b/templates/notes_folders.html @@ -8,7 +8,7 @@

    Note Folders

    - Back to Notes + Back to Notes
    diff --git a/templates/notes_list.html b/templates/notes_list.html index 379fcba..a25368f 100644 --- a/templates/notes_list.html +++ b/templates/notes_list.html @@ -8,7 +8,7 @@

    Notes

    - + ⚙️ Manage Folders
    @@ -18,7 +18,7 @@

    Notes

    - + {{ note.title }}
    @@ -234,7 +234,7 @@

    Visibility

    {% if note.tags %} {% for tag in note.get_tags_list() %} - {{ tag }} + {{ tag }} {% endfor %} {% endif %}
    - + - + @@ -258,19 +258,19 @@

    Visibility

    - + {% if note.can_user_edit(g.user) %} - + -
    From e895207ebb6e0354e3b5715dd554e56a2377ca6b Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Tue, 8 Jul 2025 11:31:53 +0200 Subject: [PATCH 11/22] Fix folder creation. --- routes/notes_api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/routes/notes_api.py b/routes/notes_api.py index 67e8e09..28ba09a 100644 --- a/routes/notes_api.py +++ b/routes/notes_api.py @@ -56,6 +56,7 @@ def api_create_folder(): folder_name = data.get('name', '').strip() parent_path = data.get('parent', '').strip() + description = data.get('description', '').strip() if not folder_name: return jsonify({'success': False, 'message': 'Folder name is required'}), 400 @@ -81,7 +82,10 @@ def api_create_folder(): # Create folder folder = NoteFolder( + name=folder_name, path=full_path, + parent_path=parent_path if parent_path else None, + description=description if description else None, company_id=g.user.company_id, created_by_id=g.user.id ) From d2ca2905fac33d505b3d4a13db624ac96361cf91 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Tue, 8 Jul 2025 12:00:40 +0200 Subject: [PATCH 12/22] Apply new style to Notes pages. --- templates/note_editor.html | 111 ++++- templates/note_mindmap.html | 575 +++++++++++++++++++----- templates/note_mindmap_backup.html | 1 + templates/note_mindmap_test.html | 8 + templates/note_mindmap_test_backup.html | 8 + templates/note_view.html | 120 ++++- templates/notes_folders.html | 132 +++++- templates/notes_list.html | 284 +++++++++--- 8 files changed, 1038 insertions(+), 201 deletions(-) create mode 100644 templates/note_mindmap_backup.html create mode 100644 templates/note_mindmap_test.html create mode 100644 templates/note_mindmap_test_backup.html diff --git a/templates/note_editor.html b/templates/note_editor.html index 8d00bdd..6573cef 100644 --- a/templates/note_editor.html +++ b/templates/note_editor.html @@ -277,6 +277,97 @@

    margin: 0 auto; } +/* Page Header - Time Tracking style */ +.page-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 16px; + padding: 2rem; + color: white; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + margin-bottom: 2rem; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 2rem; +} + +.page-title { + font-size: 2rem; + font-weight: 700; + margin: 0; + display: flex; + align-items: center; + gap: 0.75rem; +} + +.page-icon { + font-size: 2.5rem; + display: inline-block; + animation: float 3s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +.page-subtitle { + font-size: 1.1rem; + opacity: 0.9; + margin: 0.5rem 0 0 0; +} + +.header-actions { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +/* Button styles */ +.btn { + padding: 0.75rem 1.5rem; + border-radius: 8px; + font-weight: 600; + transition: all 0.3s ease; + border: 2px solid transparent; + display: inline-flex; + align-items: center; + gap: 0.5rem; + text-decoration: none; + cursor: pointer; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3); +} + +.btn-secondary { + background: white; + color: #667eea; + border-color: #e5e7eb; +} + +.btn-secondary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + border-color: #667eea; +} + +.btn .icon { + font-size: 1.1em; +} + /* Settings Panel */ .settings-panel { margin-bottom: 2rem; @@ -285,10 +376,10 @@

    .settings-card { background: white; - border: 1px solid #dee2e6; - border-radius: 8px; + border: 1px solid #e5e7eb; + border-radius: 16px; padding: 2rem; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); } .settings-grid { @@ -353,7 +444,7 @@

    } .toggle-switch input:checked + .toggle-slider { - background-color: var(--primary-color); + background-color: #667eea; } .toggle-switch input:checked + .toggle-slider:before { @@ -375,10 +466,10 @@

    /* Editor Panel */ .editor-card { background: white; - border: 1px solid #dee2e6; - border-radius: 8px; + border: 1px solid #e5e7eb; + border-radius: 16px; overflow: hidden; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); display: flex; flex-direction: column; height: 100%; @@ -474,10 +565,10 @@

    /* Preview Panel */ .preview-card { background: white; - border: 1px solid #dee2e6; - border-radius: 8px; + border: 1px solid #e5e7eb; + border-radius: 16px; overflow: hidden; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); display: flex; flex-direction: column; height: 100%; diff --git a/templates/note_mindmap.html b/templates/note_mindmap.html index b8a2863..371c1ac 100644 --- a/templates/note_mindmap.html +++ b/templates/note_mindmap.html @@ -1,57 +1,148 @@ {% extends "layout.html" %} {% block content %} -
    -
    -

    {{ note.title }} - Mind Map View

    -
    - - - - Back to Note - +
    + +
    +
    {% endblock %} \ No newline at end of file diff --git a/templates/note_mindmap_backup.html b/templates/note_mindmap_backup.html new file mode 100644 index 0000000..9edb10c --- /dev/null +++ b/templates/note_mindmap_backup.html @@ -0,0 +1 @@ +{% extends "layout.html" %}{% block content %}
    Test
    {% endblock %} \ No newline at end of file diff --git a/templates/note_mindmap_test.html b/templates/note_mindmap_test.html new file mode 100644 index 0000000..fbd468d --- /dev/null +++ b/templates/note_mindmap_test.html @@ -0,0 +1,8 @@ +{% extends "layout.html" %} + +{% block content %} +
    +

    Mind Map Test - {{ note.title }}

    +

    If you see this, the template is working.

    +
    +{% endblock %} \ No newline at end of file diff --git a/templates/note_mindmap_test_backup.html b/templates/note_mindmap_test_backup.html new file mode 100644 index 0000000..fbd468d --- /dev/null +++ b/templates/note_mindmap_test_backup.html @@ -0,0 +1,8 @@ +{% extends "layout.html" %} + +{% block content %} +
    +

    Mind Map Test - {{ note.title }}

    +

    If you see this, the template is working.

    +
    +{% endblock %} \ No newline at end of file diff --git a/templates/note_view.html b/templates/note_view.html index 4409c76..9f6d2b1 100644 --- a/templates/note_view.html +++ b/templates/note_view.html @@ -290,6 +290,98 @@

    Link to Another Note

    margin: 0 auto; } +/* Page Header - Time Tracking style */ +.page-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 16px; + padding: 2rem; + color: white; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + margin-bottom: 2rem; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: flex-start; + flex-wrap: wrap; + gap: 2rem; +} + +.page-title { + font-size: 2rem; + font-weight: 700; + margin: 0; + color: white; +} + +.header-actions { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +/* Button styles */ +.btn { + padding: 0.75rem 1.5rem; + border-radius: 8px; + font-weight: 600; + transition: all 0.3s ease; + border: 2px solid transparent; + display: inline-flex; + align-items: center; + gap: 0.5rem; + text-decoration: none; + cursor: pointer; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3); +} + +.btn-secondary { + background: white; + color: #667eea; + border-color: #e5e7eb; +} + +.btn-secondary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + border-color: #667eea; +} + +.btn-danger { + background: #ef4444; + color: white; +} + +.btn-danger:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(239, 68, 68, 0.3); +} + +.btn-success { + background: #10b981; + color: white; +} + +.btn-success:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(16, 185, 129, 0.3); +} + +.btn .icon { + font-size: 1.1em; +} + /* Page Meta */ .page-meta { display: flex; @@ -297,12 +389,12 @@

    Link to Another Note

    gap: 0.75rem; flex-wrap: wrap; font-size: 0.875rem; - color: #6c757d; + color: rgba(255, 255, 255, 0.9); margin-top: 0.5rem; } .meta-divider { - color: #dee2e6; + color: rgba(255, 255, 255, 0.5); } .pin-badge { @@ -310,20 +402,28 @@

    Link to Another Note

    align-items: center; gap: 0.25rem; padding: 0.25rem 0.75rem; - background: #fff3cd; - color: #856404; + background: rgba(255, 255, 255, 0.2); + color: white; + border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 12px; font-weight: 500; } +/* Visibility badges for header */ +.page-header .visibility-badge { + background: rgba(255, 255, 255, 0.2); + color: white; + border: 1px solid rgba(255, 255, 255, 0.3); +} + /* Metadata Card */ .metadata-card { background: white; - border: 1px solid #dee2e6; - border-radius: 8px; + border: 1px solid #e5e7eb; + border-radius: 16px; padding: 1.5rem; margin-bottom: 2rem; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); } .metadata-grid { @@ -391,11 +491,11 @@

    Link to Another Note

    /* Content Card */ .content-card { background: white; - border: 1px solid #dee2e6; - border-radius: 8px; + border: 1px solid #e5e7eb; + border-radius: 16px; padding: 2.5rem; margin-bottom: 2rem; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); min-height: 400px; } diff --git a/templates/notes_folders.html b/templates/notes_folders.html index fdb379e..d09911c 100644 --- a/templates/notes_folders.html +++ b/templates/notes_folders.html @@ -1,14 +1,27 @@ {% extends "layout.html" %} {% block content %} -
    -
    -

    Note Folders

    -
    - - Back to Notes +
    + + @@ -69,10 +82,100 @@

    Create New Folder

    + + +

    {note.title}

    +

    Created: {note.created_at.strftime('%B %d, %Y')}

    + {note.render_html()} + +""" + response = Response(html_content, mimetype='text/html') + response.headers['Content-Disposition'] = f'attachment; filename="{note.slug}.html"' + return response + + elif format == 'pdf': + # PDF download using weasyprint + try: + import weasyprint + + # Generate HTML first + html_content = f""" + + + + {note.title} + + + +

    {note.title}

    +

    Created: {note.created_at.strftime('%B %d, %Y')}

    + {note.render_html()} + +""" + + # Create temporary file for PDF + temp_file = tempfile.NamedTemporaryFile(mode='wb', suffix='.pdf', delete=False) + + # Generate PDF + weasyprint.HTML(string=html_content).write_pdf(temp_file.name) + temp_file.close() + + # Send file + response = send_file( + temp_file.name, + mimetype='application/pdf', + as_attachment=True, + download_name=f'{note.slug}.pdf' + ) + + # Clean up temp file after sending + os.unlink(temp_file.name) + + return response + + except ImportError: + # If weasyprint is not installed, return error + abort(500, "PDF generation not available") + + else: + abort(400, "Invalid format") \ No newline at end of file diff --git a/templates/note_view.html b/templates/note_view.html index 9f6d2b1..45e2e45 100644 --- a/templates/note_view.html +++ b/templates/note_view.html @@ -63,6 +63,10 @@

    {{ note.title }}

    Mind Map {% if note.can_user_edit(g.user) %} + ✏️ Edit @@ -962,4 +966,504 @@

    Link to Another Note

    {% endif %} + + + + + + + {% endblock %} \ No newline at end of file diff --git a/templates/notes/public_view.html b/templates/notes/public_view.html new file mode 100644 index 0000000..26e6d1d --- /dev/null +++ b/templates/notes/public_view.html @@ -0,0 +1,283 @@ + + + + + + {{ note.title }} + + + + + + +
    +
    +

    {{ note.title }}

    +
    + Shared by {{ note.created_by.username }} • + Created {{ note.created_at|format_date }} + {% if note.updated_at > note.created_at %} + • Updated {{ note.updated_at|format_date }} + {% endif %} +
    +
    +
    + +
    + + + + + + + + \ No newline at end of file diff --git a/templates/notes/share_password.html b/templates/notes/share_password.html new file mode 100644 index 0000000..2bb9b96 --- /dev/null +++ b/templates/notes/share_password.html @@ -0,0 +1,194 @@ + + + + + + Password Protected Note - {{ note_title }} + + + + + +
    +
    🔒
    +

    Password Protected Note

    +

    {{ note_title }}

    + +
    +
    + +
    + + +
    + + +
    +
    + + + + \ No newline at end of file From f2773a674a8461bd125443a6c6a2113cef153866 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Tue, 8 Jul 2025 21:26:33 +0200 Subject: [PATCH 14/22] Fix DB auth problems in migration scripts. --- docker-compose.yml | 22 ++++++++++++---------- migrations/run_postgres_migrations.py | 22 +++++++++++++++++----- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1d8e496..259bc97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,18 +29,20 @@ services: timetrack: build: . - environment: - FLASK_ENV: ${FLASK_ENV:-production} - SECRET_KEY: ${SECRET_KEY} - DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} - MAIL_SERVER: ${MAIL_SERVER} - MAIL_PORT: ${MAIL_PORT} - MAIL_USE_TLS: ${MAIL_USE_TLS} - MAIL_USERNAME: ${MAIL_USERNAME} - MAIL_PASSWORD: ${MAIL_PASSWORD} - MAIL_DEFAULT_SENDER: ${MAIL_DEFAULT_SENDER} ports: - "${TIMETRACK_PORT:-5000}:5000" + environment: + - DATABASE_URL=${DATABASE_URL} + - POSTGRES_HOST=db + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - FLASK_ENV=${FLASK_ENV} + - SECRET_KEY=${SECRET_KEY} + - MAIL_SERVER=${MAIL_SERVER} + - MAIL_USERNAME=${MAIL_USERNAME} + - MAIL_PASSWORD=${MAIL_PASSWORD} + - MAIL_DEFAULT_SENDER=${MAIL_DEFAULT_SENDER} depends_on: db: condition: service_healthy diff --git a/migrations/run_postgres_migrations.py b/migrations/run_postgres_migrations.py index ff28f71..681b323 100755 --- a/migrations/run_postgres_migrations.py +++ b/migrations/run_postgres_migrations.py @@ -53,16 +53,28 @@ def run_migration(migration_file): # Check if it's a SQL file if migration_file.endswith('.sql'): # Run SQL file using psql - import os - db_host = os.environ.get('POSTGRES_HOST', 'db') - db_name = os.environ.get('POSTGRES_DB', 'timetrack') - db_user = os.environ.get('POSTGRES_USER', 'timetrack') + # Try to parse DATABASE_URL first, fall back to individual env vars + database_url = os.environ.get('DATABASE_URL') + if database_url: + # Parse DATABASE_URL: postgresql://user:password@host:port/dbname + from urllib.parse import urlparse + parsed = urlparse(database_url) + db_host = parsed.hostname or 'db' + db_port = parsed.port or 5432 + db_name = parsed.path.lstrip('/') or 'timetrack' + db_user = parsed.username or 'timetrack' + db_password = parsed.password or 'timetrack' + else: + db_host = os.environ.get('POSTGRES_HOST', 'db') + db_name = os.environ.get('POSTGRES_DB', 'timetrack') + db_user = os.environ.get('POSTGRES_USER', 'timetrack') + db_password = os.environ.get('POSTGRES_PASSWORD', 'timetrack') result = subprocess.run( ['psql', '-h', db_host, '-U', db_user, '-d', db_name, '-f', script_path], capture_output=True, text=True, - env={**os.environ, 'PGPASSWORD': os.environ.get('POSTGRES_PASSWORD', 'timetrack')} + env={**os.environ, 'PGPASSWORD': db_password} ) else: # Run Python migration script From 9dd208800d85fffd2a0975ce7f9025fa910f5a48 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Tue, 8 Jul 2025 22:35:31 +0200 Subject: [PATCH 15/22] Switch Emojis to Tabler icons. --- static/css/auth.css | 3 +- static/css/style.css | 2 +- static/js/auth-animations.js | 6 +- static/js/password-strength.js | 4 +- templates/about.html | 16 ++--- templates/admin_company.html | 49 +++++++------ templates/admin_projects.html | 56 +++++++-------- templates/admin_teams.html | 22 +++--- templates/admin_users.html | 2 +- templates/analytics.html | 8 +-- templates/company_users.html | 4 +- templates/config.html | 36 +++++----- templates/confirm_company_deletion.html | 12 ++-- templates/dashboard.html | 2 +- templates/edit_project.html | 2 +- templates/imprint.html | 2 +- templates/index.html | 26 +++---- templates/index_old.html | 26 +++---- templates/invitations/send.html | 4 +- templates/layout.html | 51 +++++++------- templates/note_editor.html | 48 ++++++------- templates/note_mindmap.html | 2 +- templates/note_view.html | 60 ++++++++-------- templates/notes_folders.html | 10 +-- templates/notes_list.html | 68 ++++++++++++------- templates/profile.html | 8 +-- templates/register.html | 2 +- templates/register_invitation.html | 4 +- templates/setup_company.html | 10 +-- templates/system_admin_announcement_form.html | 2 +- templates/system_admin_branding.html | 4 +- templates/system_admin_companies.html | 10 +-- templates/system_admin_company_detail.html | 16 ++--- templates/system_admin_dashboard.html | 36 +++++----- templates/system_admin_edit_user.html | 2 +- templates/system_admin_health.html | 24 +++---- templates/system_admin_settings.html | 4 +- templates/system_admin_time_entries.html | 6 +- templates/system_admin_users.html | 8 +-- templates/task_modal.html | 22 +++--- templates/team_form.html | 6 +- templates/time_tracking.html | 38 +++++------ templates/unified_task_management.html | 30 ++++---- templates/verify_2fa.html | 2 +- 44 files changed, 389 insertions(+), 366 deletions(-) diff --git a/static/css/auth.css b/static/css/auth.css index d99f996..6bebcc4 100644 --- a/static/css/auth.css +++ b/static/css/auth.css @@ -367,7 +367,8 @@ body.auth-page::after { } .company-code-group::before { - content: '🏢'; + content: '\eebe'; /* Tabler icon building */ + font-family: 'tabler-icons'; position: absolute; left: 1rem; top: 2.5rem; /* Position below the label */ diff --git a/static/css/style.css b/static/css/style.css index 7c9b0aa..77e34a8 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -242,7 +242,7 @@ button { .nav-icon { margin-right: 0.75rem; - font-size: 1.1rem; + font-size: 1.5rem; min-width: 20px; text-align: center; } diff --git a/static/js/auth-animations.js b/static/js/auth-animations.js index f213f3a..567a823 100644 --- a/static/js/auth-animations.js +++ b/static/js/auth-animations.js @@ -103,7 +103,7 @@ document.addEventListener('DOMContentLoaded', function() { const toggleBtn = document.createElement('button'); toggleBtn.type = 'button'; toggleBtn.className = 'password-toggle'; - toggleBtn.innerHTML = '👁️'; + toggleBtn.innerHTML = ''; toggleBtn.style.cssText = ` position: absolute; right: 1rem; @@ -120,10 +120,10 @@ document.addEventListener('DOMContentLoaded', function() { toggleBtn.addEventListener('click', function() { if (input.type === 'password') { input.type = 'text'; - this.innerHTML = '🙈'; + this.innerHTML = ''; } else { input.type = 'password'; - this.innerHTML = '👁️'; + this.innerHTML = ''; } }); diff --git a/static/js/password-strength.js b/static/js/password-strength.js index f5e0f8b..b3c2f68 100644 --- a/static/js/password-strength.js +++ b/static/js/password-strength.js @@ -148,10 +148,10 @@ document.addEventListener('DOMContentLoaded', function() { matchIndicator.textContent = ''; matchIndicator.className = 'password-match-indicator'; } else if (password === confirmInput.value) { - matchIndicator.textContent = '✓ Passwords match'; + matchIndicator.innerHTML = ' Passwords match'; matchIndicator.className = 'password-match-indicator match'; } else { - matchIndicator.textContent = '✗ Passwords do not match'; + matchIndicator.innerHTML = ' Passwords do not match'; matchIndicator.className = 'password-match-indicator no-match'; } } diff --git a/templates/about.html b/templates/about.html index 2248ad9..886e371 100644 --- a/templates/about.html +++ b/templates/about.html @@ -23,17 +23,17 @@

    ⏸️ Smart Break Management

    -

    👥 Team Management

    +

    Team Management

    Managers can organize users into teams, monitor team performance, and track collective working hours. Role-based access ensures appropriate permissions for different user levels.

    -

    📊 Comprehensive Reporting

    +

    Comprehensive Reporting

    View detailed time entry history, team performance metrics, and individual productivity reports. Export data for payroll and project management purposes.

    -

    🔧 Flexible Configuration

    +

    Flexible Configuration

    Customize work hour requirements, mandatory break durations, and threshold settings to match your organization's policies and labor regulations.

    @@ -80,22 +80,22 @@

    Why Choose {{ g.branding.app_name }}?

    -

    ✅ Compliance Ready

    +

    Compliance Ready

    Automatically enforces break policies and work hour regulations to help your organization stay compliant with labor laws.

    -

    ✅ Easy to Use

    +

    Easy to Use

    Intuitive interface requires minimal training. Start tracking time immediately without complicated setup procedures.

    -

    ✅ Scalable Solution

    +

    Scalable Solution

    Grows with your organization from small teams to large enterprises. Multi-tenant architecture supports multiple companies, complex organizational structures, and unlimited growth potential.

    -

    ✅ Data-Driven Insights

    +

    Data-Driven Insights

    Generate meaningful reports and analytics to optimize productivity, identify trends, and make informed business decisions.

    @@ -115,7 +115,7 @@

    🏢 New Company Setup

    -

    👥 Join Existing Company

    +

    Join Existing Company

    Already have a company using {{ g.branding.app_name }}? Get your company code from your administrator and register to join your organization.

    diff --git a/templates/admin_company.html b/templates/admin_company.html index b3e8ca6..57f363c 100644 --- a/templates/admin_company.html +++ b/templates/admin_company.html @@ -7,7 +7,7 @@

    - 🏢 + Company Management

    Configure your company settings and policies

    @@ -26,22 +26,22 @@

    {{ stats.total_users }}
    Total Users
    - View all → + View all
    {{ stats.total_teams }}
    Teams
    - Manage → + Manage
    {{ stats.total_projects }}
    Total Projects
    - View all → + View all
    {{ stats.active_projects }}
    Active Projects
    - Manage → + Manage

    @@ -53,7 +53,7 @@

    - ℹ️ + Company Information

    @@ -97,13 +97,13 @@

    - 🔑 +
    @@ -111,7 +111,7 @@

    - 📅 +
    {{ company.created_at.strftime('%B %d, %Y at %I:%M %p') }} @@ -121,7 +121,7 @@

    @@ -133,14 +133,14 @@

    - + Quick Actions

    -
    👥
    +

    Manage Users

    User accounts & permissions

    @@ -148,7 +148,7 @@

    Manage Users

    -
    👨‍👩‍👧‍👦
    +

    Manage Teams

    Organize company structure

    @@ -156,7 +156,7 @@

    Manage Teams

    -
    📁
    +

    Manage Projects

    Time tracking projects

    @@ -164,7 +164,7 @@

    Manage Projects

    -
    📨
    +

    Send Invitation

    Invite team members

    @@ -181,7 +181,7 @@

    Send Invitation

    - 📋 + Work Policies

    @@ -251,7 +251,7 @@

    @@ -264,7 +264,7 @@

    - 👤 + User Registration

    @@ -304,7 +304,7 @@

    Require Email Verification

    @@ -728,6 +728,11 @@

    Require Email Verification

    flex-shrink: 0; } +.action-icon i { + font-size: 2rem; + color: #667eea; +} + .action-content h3 { font-size: 1.05rem; font-weight: 600; @@ -874,17 +879,17 @@

    Require Email Verification

    const copyText = document.getElementById('copyText'); // Store original values - const originalIcon = copyIcon.textContent; + const originalIcon = copyIcon.innerHTML; const originalText = copyText.textContent; // Update to success state - copyIcon.textContent = '✓'; + copyIcon.innerHTML = ''; copyText.textContent = 'Copied!'; button.classList.add('success'); // Reset after 2 seconds setTimeout(() => { - copyIcon.textContent = originalIcon; + copyIcon.innerHTML = originalIcon; copyText.textContent = originalText; button.classList.remove('success'); }, 2000); diff --git a/templates/admin_projects.html b/templates/admin_projects.html index 2c400ba..e2e163b 100644 --- a/templates/admin_projects.html +++ b/templates/admin_projects.html @@ -7,18 +7,18 @@

    - 📁 + Project Management

    Manage projects, categories, and track time entries

    - + + Create New Project
    @@ -51,7 +51,7 @@

    {% else %}
    -
    🏷️
    +

    No categories created yet

    @@ -102,7 +102,7 @@

    - 🔍 +
    @@ -141,18 +141,18 @@

    {{ project.name }}

    {% if project.category %}
    - {{ project.category.icon or '📁' }} {{ project.category.name }} + {{ project.category.icon or '' }} {{ project.category.name }}
    {% endif %}
    - 👥 + {{ project.team.name if project.team else 'All Teams' }}
    - 📅 + {% if project.start_date %} {{ project.start_date.strftime('%b %d, %Y') }} @@ -163,11 +163,11 @@

    {{ project.name }}

    - ⏱️ + {{ project.time_entries|length }} time entries
    - 👤 + Created by {{ project.created_by.username }}
    @@ -175,18 +175,18 @@

    {{ project.name }}

    - ✏️ + Edit - 📋 + Tasks {% if g.user.role in [Role.ADMIN, Role.SYSTEM_ADMIN] %}
    {% endif %} @@ -226,7 +226,7 @@

    {{ project.name }}

    {% if project.category %} - {{ project.category.icon or '📁' }} {{ project.category.name }} + {{ project.category.icon or '' }} {{ project.category.name }} {% else %} - @@ -251,16 +251,16 @@

    {{ project.name }}

    - ✏️ + - 📋 + {% if g.user.role in [Role.ADMIN, Role.SYSTEM_ADMIN] %}
    {% endif %} @@ -275,18 +275,18 @@

    {{ project.name }}

    {% else %}
    -
    📁
    +

    No Projects Yet

    Create your first project to start tracking time

    - + + Create First Project
    @@ -337,7 +337,7 @@ @@ -1203,7 +1203,7 @@ manageCategoriesBtn.addEventListener('click', function() { const isVisible = categoriesSection.style.display !== 'none'; categoriesSection.style.display = isVisible ? 'none' : 'block'; - this.innerHTML = isVisible ? '🏷️ Manage Categories' : '🏷️ Hide Categories'; + this.innerHTML = isVisible ? ' Manage Categories' : ' Hide Categories'; }); // View Toggle diff --git a/templates/admin_teams.html b/templates/admin_teams.html index a9c5e56..0fa49bc 100644 --- a/templates/admin_teams.html +++ b/templates/admin_teams.html @@ -7,14 +7,14 @@

    - 👥 + Team Management

    Manage teams and their members across your organization

    @@ -45,7 +45,7 @@

    - 🔍 +
    - 👥 +
    {{ team.users|length }} members @@ -74,12 +74,12 @@

    {{ team.name }}

    - 📅 + Created {{ team.created_at.strftime('%b %d, %Y') }}
    {% if team.users %}
    - 👤 + Led by {{ team.users[0].username }}
    {% endif %} @@ -104,7 +104,7 @@

    {{ team.name }}

    class="delete-form" onsubmit="return confirm('Are you sure you want to delete the team \"{{ team.name }}\"? This action cannot be undone.');">
    @@ -122,18 +122,18 @@

    {{ team.name }}

    {% else %}
    -
    👥
    +

    No Teams Yet

    Create your first team to start organizing your workforce

    - + + Create First Team
    diff --git a/templates/admin_users.html b/templates/admin_users.html index 829728d..25279a1 100644 --- a/templates/admin_users.html +++ b/templates/admin_users.html @@ -230,7 +230,7 @@
    {{ user.username }} {% if user.two_factor_enabled %} - 🔒 + {% endif %} {{ user.email }}
    @@ -45,7 +45,7 @@

    🏢 Company Information

    {% if users %}
    -

    👥 Users ({{ users|length }})

    +

    Users ({{ users|length }})

    Company Name:
    @@ -83,7 +83,7 @@

    👥 Users ({{ users|length }})

    {% if teams %}
    -

    🏭 Teams ({{ teams|length }})

    +

    Teams ({{ teams|length }})

    @@ -141,7 +141,7 @@

    📝 Projects ({{ projects|length }})

    {% if tasks %}
    -

    ✅ Tasks ({{ tasks|length }})

    +

    Tasks ({{ tasks|length }})

    diff --git a/templates/dashboard.html b/templates/dashboard.html index 5763546..0d4ae8f 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -21,7 +21,7 @@

    My Dashboard

    -
    +
    Unlimited Tracking
    @@ -115,14 +115,14 @@

    Forever Free, Forever Open

    {{ g.branding.app_name if g.branding else 'TimeTrack' }} Community

    $0/forever
      -
    • ✓ Unlimited users
    • -
    • ✓ All features included
    • -
    • ✓ Time tracking & analytics
    • -
    • ✓ Sprint management
    • -
    • ✓ Team collaboration
    • -
    • ✓ Project management
    • -
    • ✓ Self-hosted option
    • -
    • ✓ No restrictions
    • +
    • Unlimited users
    • +
    • All features included
    • +
    • Time tracking & analytics
    • +
    • Sprint management
    • +
    • Team collaboration
    • +
    • Project management
    • +
    • Self-hosted option
    • +
    • No restrictions
    Get Started Free
    @@ -376,7 +376,7 @@

    diff --git a/templates/index_old.html b/templates/index_old.html index 14ac3ce..3fd73c5 100644 --- a/templates/index_old.html +++ b/templates/index_old.html @@ -31,12 +31,12 @@

    Transform Your Productivity

    Powerful Features for Modern Teams

    -
    +

    Lightning Fast

    Start tracking in seconds with our intuitive one-click interface

    -
    📊
    +

    Advanced Analytics

    Gain insights with comprehensive reports and visual dashboards

    @@ -46,7 +46,7 @@

    Sprint Management

    Organize work into sprints with agile project tracking

    -
    👥
    +

    Team Collaboration

    Manage teams, projects, and resources all in one place

    @@ -71,7 +71,7 @@

    Why Choose {{ g.branding.app_name if g.branding else '
    Free & Open Source
    -
    +
    Unlimited Tracking
    @@ -115,14 +115,14 @@

    Forever Free, Forever Open

    {{ g.branding.app_name if g.branding else 'TimeTrack' }} Community

    $0/forever
      -
    • ✓ Unlimited users
    • -
    • ✓ All features included
    • -
    • ✓ Time tracking & analytics
    • -
    • ✓ Sprint management
    • -
    • ✓ Team collaboration
    • -
    • ✓ Project management
    • -
    • ✓ Self-hosted option
    • -
    • ✓ No restrictions
    • +
    • Unlimited users
    • +
    • All features included
    • +
    • Time tracking & analytics
    • +
    • Sprint management
    • +
    • Team collaboration
    • +
    • Project management
    • +
    • Self-hosted option
    • +
    • No restrictions
    Get Started Free
    @@ -376,7 +376,7 @@

    diff --git a/templates/invitations/send.html b/templates/invitations/send.html index 5425667..4b5aab3 100644 --- a/templates/invitations/send.html +++ b/templates/invitations/send.html @@ -14,7 +14,7 @@

    @@ -26,7 +26,7 @@

    - 👥 + Invitation Details

    diff --git a/templates/layout.html b/templates/layout.html index 35b15f0..4ce2048 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -6,6 +6,7 @@ {{ title }} - {{ g.branding.app_name if g.branding else 'TimeTrack' }}{% if g.company %} - {{ g.company.name }}{% endif %} + {% if not g.user %} {% endif %} @@ -25,7 +26,7 @@ border-color: {{ (g.branding.primary_color if g.branding else '#007bff') + 'dd' }}; } .nav-icon { - color: var(--primary-color); + color: rgb(116, 85, 175) /* var(--primary-color); */ } a:hover { color: var(--primary-color); @@ -116,10 +117,10 @@

    {{ g.user.username }}

    @@ -128,38 +129,38 @@

    {{ g.user.username }}

    @@ -210,7 +211,7 @@

    {{ g.user.username }}

    {% if g.show_email_nag %} @@ -256,7 +256,7 @@

    @@ -275,7 +275,7 @@

    {% else %}
    -
    📝
    +

    No notes found

    {% if folder_filter or tag_filter or visibility_filter or search_query %} @@ -821,6 +821,22 @@

    No notes found

    margin-bottom: 20px; } +.empty-icon i { + font-size: 3rem; + color: #667eea; +} + +/* Icon styling */ +.folder-icon i, .tag-icon i, .visibility-link i, +.filter-tag i, .note-meta i, .search-btn i, +.page-icon i { + font-size: 1em; +} + +.btn .icon i { + font-size: 1.1em; +} + /* List View / Table */ .notes-table { width: 100%; diff --git a/templates/profile.html b/templates/profile.html index b288bde..1a229ce 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -20,7 +20,7 @@

    {{ user.username }}

    {{ user.company.name if user.company else 'No Company' }}
    - 👥 + {{ user.team.name if user.team else 'No Team' }}
    @@ -81,7 +81,7 @@

    - 💡 +

    Your default avatar is automatically generated based on your username.

    -

    ✅ Your email is pre-verified through this invitation

    +

    Your email is pre-verified through this invitation

    diff --git a/templates/setup_company.html b/templates/setup_company.html index 5ef5010..ab55da8 100644 --- a/templates/setup_company.html +++ b/templates/setup_company.html @@ -13,17 +13,17 @@

    {% if is_initial_setup %}
    -

    🎉 Let's Get Started!

    +

    Let's Get Started!

    Set up your company and create the first administrator account to begin using {{ g.branding.app_name }}.

    {% elif is_super_admin %}
    -

    🏢 New Company Setup

    +

    New Company Setup

    Create a new company with its own administrator. This will be a separate organization within {{ g.branding.app_name }}.

    {% else %}
    -

    ⚠️ Access Denied

    +

    Access Denied

    You do not have permission to create new companies.

    Return Home
    @@ -102,11 +102,11 @@

    Administrator Privileges

    {% if is_super_admin %} - ← Back to Dashboard + Back to Dashboard {% endif %}
    diff --git a/templates/system_admin_announcement_form.html b/templates/system_admin_announcement_form.html index ca533c5..fdc9551 100644 --- a/templates/system_admin_announcement_form.html +++ b/templates/system_admin_announcement_form.html @@ -6,7 +6,7 @@

    {{ "Edit" if announcement else "Create" }} Announcement

    {{ "Update" if announcement else "Create new" }} system announcement for users

    - ← Back to Announcements + Back to Announcements diff --git a/templates/system_admin_branding.html b/templates/system_admin_branding.html index 7b84269..3c3b513 100644 --- a/templates/system_admin_branding.html +++ b/templates/system_admin_branding.html @@ -5,7 +5,7 @@

    🎨 Branding Settings

    @@ -43,7 +43,7 @@

    Live Preview

    -

    🔧 Branding Configuration

    +

    Branding Configuration

    diff --git a/templates/system_admin_companies.html b/templates/system_admin_companies.html index 6c780ba..f96558c 100644 --- a/templates/system_admin_companies.html +++ b/templates/system_admin_companies.html @@ -3,11 +3,11 @@ {% block content %}
    -

    🏢 System Admin - All Companies

    +

    System Admin - All Companies

    Manage companies across the entire system

    @@ -75,7 +75,7 @@

    🏢 System Admin - All Companies

    @@ -111,7 +111,7 @@

    No companies found

    -

    📊 Company Summary

    +

    Company Summary

    Total Companies

    diff --git a/templates/system_admin_company_detail.html b/templates/system_admin_company_detail.html index 69db2e7..265ddf0 100644 --- a/templates/system_admin_company_detail.html +++ b/templates/system_admin_company_detail.html @@ -3,17 +3,17 @@ {% block content %}
    -

    🏢 {{ company.name }}

    +

    {{ company.name }}

    Company Details - System Administrator View

    -

    📋 Company Information

    +

    Company Information

    @@ -58,7 +58,7 @@

    📋 Company Information

    -

    📊 Company Statistics

    +

    Company Statistics

    {{ users|length }}

    @@ -83,7 +83,7 @@

    {{ recent_time_entries }}

    {% if role_counts %}
    -

    👥 Role Distribution

    +

    Role Distribution

    {% for role, count in role_counts.items() %}
    @@ -122,7 +122,7 @@

    👤 Users ({{ users|length }})

    {% if users|length > 10 %} {% endif %} @@ -196,7 +196,7 @@

    📝 Projects ({{ projects|length }})

    🛠️ Management Actions

    -
    👥
    +

    Manage Users

    View and edit all users in this company

    @@ -214,7 +214,7 @@

    View Time Entries

    -

    ⚠️ Danger Zone

    +

    Danger Zone

    Delete Company

    Once you delete a company, there is no going back. This will permanently delete:

    diff --git a/templates/system_admin_dashboard.html b/templates/system_admin_dashboard.html index e6802aa..f915d0f 100644 --- a/templates/system_admin_dashboard.html +++ b/templates/system_admin_dashboard.html @@ -2,22 +2,22 @@ {% block content %}
    -

    🔧 System Administrator Dashboard

    +

    System Administrator Dashboard

    Global system overview and management tools

    -

    📊 System Overview

    +

    System Overview

    {{ total_users }}

    Total Users

    - Manage → + Manage

    {{ total_teams }}

    @@ -30,14 +30,14 @@

    {{ total_projects }}

    {{ total_time_entries }}

    Time Entries

    - View → + View
    -

    👤 Administrator Overview

    +

    Administrator Overview

    {{ system_admins }}

    @@ -51,7 +51,7 @@

    {{ regular_admins }}

    {{ blocked_users }}

    Blocked Users

    {% if blocked_users > 0 %} - Review → + Review {% endif %}
    @@ -59,7 +59,7 @@

    {{ blocked_users }}

    -

    📈 Recent Activity (Last 7 Days)

    +

    Recent Activity (Last 7 Days)

    {{ recent_users }}

    @@ -79,7 +79,7 @@

    {{ recent_time_entries }}

    {% if orphaned_users > 0 or orphaned_time_entries > 0 %}
    -

    ⚠️ System Health Issues

    +

    System Health Issues

    {% if orphaned_users > 0 %}
    @@ -102,7 +102,7 @@

    {{ orphaned_time_entries }}

    -

    🏢 Top Companies by Users

    +

    Top Companies by Users

    {% if top_companies %}
    {{ entry.arrival_time|format_time }} - + {{ entry.departure_time|format_time if entry.departure_time else 'Active' }}
    {{ entry.arrival_time|format_time }} - + {{ entry.departure_time|format_time if entry.departure_time else 'Active' }}
    - {% if note.is_pinned %}📌{% endif %} + {% if note.is_pinned %}{% endif %} {{ note.title }} {{ note.folder or '-' }} - {% if note.visibility.value == 'Private' %}🔒{% elif note.visibility.value == 'Team' %}👥{% else %}🏢{% endif %} + {% if note.visibility.value == 'Private' %}{% elif note.visibility.value == 'Team' %}{% else %}{% endif %} {{ note.visibility.value }}
    @@ -131,7 +131,7 @@

    🏢 Top Companies by Users

    -

    🆕 Recent Companies

    +

    Recent Companies

    {% if recent_companies_list %}
    @@ -169,25 +169,25 @@

    🆕 Recent Companies

    diff --git a/templates/system_admin_edit_user.html b/templates/system_admin_edit_user.html index b77174c..2869744 100644 --- a/templates/system_admin_edit_user.html +++ b/templates/system_admin_edit_user.html @@ -5,7 +5,7 @@

    ✏️ Edit User: {{ user.username }}

    System Administrator - Edit user across companies

    - ← Back to Users + Back to Users
    diff --git a/templates/system_admin_health.html b/templates/system_admin_health.html index 531ad77..ba4f64c 100644 --- a/templates/system_admin_health.html +++ b/templates/system_admin_health.html @@ -3,23 +3,23 @@ {% block content %}
    -

    🏥 System Health Check

    +

    System Health Check

    System diagnostics and event monitoring

    - ← Back to Dashboard + Back to Dashboard
    -

    🔍 System Status

    +

    System Status

    {% if health_summary.health_status == 'healthy' %} - ✅ + {% elif health_summary.health_status == 'issues' %} - ⚠️ + {% else %} - ❌ + {% endif %}
    @@ -39,7 +39,7 @@

    Overall Health

    - {% if db_healthy %}✅{% else %}❌{% endif %} + {% if db_healthy %}{% else %}{% endif %}

    Database

    @@ -55,7 +55,7 @@

    Database

    -
    ⏱️
    +

    Uptime

    {{ uptime_duration.days }}d {{ uptime_duration.seconds//3600 }}h {{ (uptime_duration.seconds//60)%60 }}m

    @@ -67,7 +67,7 @@

    Uptime

    -

    📊 Event Statistics

    +

    Event Statistics

    {{ health_summary.errors_24h }}

    @@ -91,7 +91,7 @@

    {{ health_summary.total_events_week }}

    {% if errors %}
    -

    🚨 Recent Errors

    +

    Recent Errors

    {% for error in errors %}
    @@ -115,7 +115,7 @@

    🚨 Recent Errors

    {% if warnings %}
    -

    ⚠️ Recent Warnings

    +

    Recent Warnings

    {% for warning in warnings %}
    @@ -138,7 +138,7 @@

    ⚠️ Recent Warnings

    -

    📋 System Event Log (Last 7 Days)

    +

    System Event Log (Last 7 Days)

    diff --git a/templates/system_admin_settings.html b/templates/system_admin_settings.html index 461f06c..5dbd8ca 100644 --- a/templates/system_admin_settings.html +++ b/templates/system_admin_settings.html @@ -5,7 +5,7 @@

    ⚙️ System Administrator Settings

    Global system configuration and management

    - ← Back to Dashboard + Back to Dashboard
    @@ -168,7 +168,7 @@

    System Health Check

    🚀 Quick Actions

    -
    👥
    +

    Manage All Users

    View, edit, and manage users across all companies

    diff --git a/templates/system_admin_time_entries.html b/templates/system_admin_time_entries.html index a65496a..cc9a8bf 100644 --- a/templates/system_admin_time_entries.html +++ b/templates/system_admin_time_entries.html @@ -5,7 +5,7 @@
    @@ -112,7 +112,7 @@

    Filter Time Entries

    diff --git a/templates/system_admin_users.html b/templates/system_admin_users.html index 8839660..1a80ee3 100644 --- a/templates/system_admin_users.html +++ b/templates/system_admin_users.html @@ -3,9 +3,9 @@ {% block content %}
    -

    👥 System Admin - All Users

    +

    System Admin - All Users

    Manage users across all companies

    - ← Back to Dashboard + Back to Dashboard
    @@ -124,7 +124,7 @@

    Filter Users

    diff --git a/templates/task_modal.html b/templates/task_modal.html index c772030..8b4b8ac 100644 --- a/templates/task_modal.html +++ b/templates/task_modal.html @@ -11,7 +11,7 @@
    -

    📝 Basic Information

    +

    Basic Information

    @@ -48,7 +48,7 @@

    📝 Basic Information

    -

    👥 Assignment & Planning

    +

    Assignment & Planning

    @@ -89,7 +89,7 @@

    👥 Assignment & Planning

    - +
    @@ -97,11 +97,11 @@

    👥 Assignment & Planning

    -

    🔗 Dependencies

    +

    Dependencies

    -

    🚫 Blocked By

    +

    Blocked By

    Tasks that must be completed before this task can start

    @@ -114,7 +114,7 @@

    🚫 Blocked By

    -

    🔒 Blocks

    +

    Blocks

    Tasks that cannot start until this task is completed

    @@ -129,17 +129,17 @@

    🔒 Blocks

    -

    📋 Subtasks

    +

    Subtasks

    - +
    {% else %}
    -
    👥
    +

    No members in this team yet

    Add members using the form below

    diff --git a/templates/time_tracking.html b/templates/time_tracking.html index 7e47aaa..20b6c2e 100644 --- a/templates/time_tracking.html +++ b/templates/time_tracking.html @@ -7,18 +7,18 @@

    - ⏱️ + Time Tracking

    Track your work hours efficiently

    - 📊 + View Analytics
    @@ -96,15 +96,15 @@

    @@ -152,7 +152,7 @@

    Start Tracking Time

    @@ -186,16 +186,16 @@

    Start Tracking Time

    - 📋 + Recent Time Entries

    @@ -229,7 +229,7 @@

    @@ -269,19 +269,19 @@

    {% if entry.departure_time and not active_entry %} {% if entry.arrival_time.date() >= today %} {% else %} {% endif %} {% endif %} @@ -292,7 +292,7 @@

    {% else %}
    -
    📭
    +

    No time entries yet

    Start tracking your time to see entries here

    @@ -322,18 +322,18 @@

    No time entries yet

    {% if entry.task %}
    - 📋 {{ entry.task.title }} + {{ entry.task.title }}
    {% endif %}
    - 🕐 + {{ entry.arrival_time|format_time }} - {{ entry.departure_time|format_time if entry.departure_time else 'Active' }}
    {% if entry.notes %}
    - 📝 + {{ entry.notes }}
    {% endif %} diff --git a/templates/unified_task_management.html b/templates/unified_task_management.html index 6c005b9..bb5e9bb 100644 --- a/templates/unified_task_management.html +++ b/templates/unified_task_management.html @@ -4,7 +4,7 @@
    -

    📋 Task Management

    +

    Task Management

    @@ -19,9 +19,9 @@

    📋 Task Management

    - - - + + +
    @@ -54,7 +54,7 @@

    📋 Task Management

    -

    📝 To Do

    +

    To Do

    0
    @@ -64,7 +64,7 @@

    📝 To Do

    -

    ⚡ In Progress

    +

    In Progress

    0
    @@ -74,7 +74,7 @@

    ⚡ In Progress

    -

    🔍 In Review

    +

    In Review

    0
    @@ -84,7 +84,7 @@

    🔍 In Review

    -

    ✅ Done

    +

    Done

    0
    @@ -94,7 +94,7 @@

    ✅ Done

    -

    ❌ Cancelled

    +

    Cancelled

    0
    @@ -104,7 +104,7 @@

    ❌ Cancelled

    From 77c2827fee387033f05a51d679b71fd53e89e758 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Tue, 8 Jul 2025 22:40:36 +0200 Subject: [PATCH 16/22] Remove local file. --- .claude/settings.local.json | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 5486777..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(docker exec:*)", - "Bash(docker restart:*)", - "Bash(docker logs:*)", - "Bash(grep:*)", - "Bash(chmod:*)", - "Bash(python:*)", - "Bash(pkill:*)", - "Bash(curl:*)" - ], - "deny": [] - } -} \ No newline at end of file From e95973497371ed15b18539a11335e279afb08017 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Wed, 9 Jul 2025 06:45:31 +0200 Subject: [PATCH 17/22] Stramline template's use and adjust styl.es. --- static/css/hover-standards.css | 390 +++++++ static/css/splash.css | 26 +- static/css/style.css | 143 +-- static/css/time-tracking.css | 770 ++++++++++++++ templates/_time_tracking_interface.html | 356 +++++++ templates/_time_tracking_modals.html | 146 +++ templates/admin_users.html | 26 +- templates/index.html | 1242 +---------------------- templates/invitations/list.html | 32 +- templates/invitations/send.html | 8 +- templates/layout.html | 25 +- templates/notes_list.html | 8 +- templates/profile.html | 36 +- templates/team_form.html | 10 +- templates/time_tracking.html | 1233 +--------------------- templates/unified_task_management.html | 2 +- 16 files changed, 1844 insertions(+), 2609 deletions(-) create mode 100644 static/css/hover-standards.css create mode 100644 static/css/time-tracking.css create mode 100644 templates/_time_tracking_interface.html create mode 100644 templates/_time_tracking_modals.html diff --git a/static/css/hover-standards.css b/static/css/hover-standards.css new file mode 100644 index 0000000..35fb46c --- /dev/null +++ b/static/css/hover-standards.css @@ -0,0 +1,390 @@ +/* =================================================================== + TIMETRACK HOVER STANDARDS + Consistent hover states based on primary gradient colors + Primary: #667eea to #764ba2 + =================================================================== */ + +:root { + /* Primary gradient colors */ + --primary-gradient-start: #667eea; + --primary-gradient-end: #764ba2; + --primary-color: #667eea; + + /* Hover color variations */ + --hover-primary: #5569d6; /* Darker primary for hover */ + --hover-primary-dark: #4a5bc8; /* Even darker primary */ + --hover-secondary: #6a4195; /* Darker gradient end */ + + /* Background hover colors */ + --hover-bg-light: rgba(102, 126, 234, 0.05); /* 5% primary */ + --hover-bg-medium: rgba(102, 126, 234, 0.1); /* 10% primary */ + --hover-bg-strong: rgba(102, 126, 234, 0.15); /* 15% primary */ + + /* Shadow definitions */ + --hover-shadow-light: 0 2px 4px rgba(102, 126, 234, 0.15); + --hover-shadow-medium: 0 4px 12px rgba(102, 126, 234, 0.2); + --hover-shadow-strong: 0 6px 20px rgba(102, 126, 234, 0.25); + --hover-shadow-heavy: 0 8px 30px rgba(102, 126, 234, 0.3); + + /* Transform values */ + --hover-lift-subtle: translateY(-1px); + --hover-lift-small: translateY(-2px); + --hover-lift-medium: translateY(-3px); + --hover-lift-large: translateY(-5px); + + /* Transition timing */ + --hover-transition-fast: all 0.2s ease; + --hover-transition-normal: all 0.3s ease; + --hover-transition-smooth: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* =================================================================== + GLOBAL HOVER STYLES + =================================================================== */ + +/* All links */ +a { + transition: var(--hover-transition-fast); +} + +a:hover { + color: var(--primary-color); + text-decoration: none; +} + +/* =================================================================== + BUTTON HOVER STYLES + =================================================================== */ + +/* Base button hover */ +.btn { + transition: var(--hover-transition-normal); +} + +.btn:hover { + transform: var(--hover-lift-subtle); + box-shadow: var(--hover-shadow-light); +} + +/* Primary button with gradient */ +.btn-primary { + background: linear-gradient(135deg, var(--primary-gradient-start) 0%, var(--primary-gradient-end) 100%); +} + +.btn-primary:hover { + background: linear-gradient(135deg, var(--hover-primary) 0%, var(--hover-secondary) 100%); + transform: var(--hover-lift-small); + box-shadow: var(--hover-shadow-medium); + border-color: var(--hover-primary); +} + +/* Secondary button */ +.btn-secondary:hover { + background-color: var(--hover-bg-medium); + border-color: var(--primary-color); + color: var(--primary-color); + transform: var(--hover-lift-subtle); + box-shadow: var(--hover-shadow-light); +} + +/* Success button - maintain green but with consistent effects */ +.btn-success:hover { + transform: var(--hover-lift-small); + box-shadow: var(--hover-shadow-medium); + filter: brightness(0.9); +} + +/* Danger button - maintain red but with consistent effects */ +.btn-danger:hover { + transform: var(--hover-lift-small); + box-shadow: var(--hover-shadow-medium); + filter: brightness(0.9); +} + +/* Outline buttons */ +.btn-outline:hover { + background-color: var(--primary-color); + border-color: var(--primary-color); + color: white; + transform: var(--hover-lift-subtle); + box-shadow: var(--hover-shadow-light); +} + +/* Small buttons */ +.btn-sm:hover { + transform: var(--hover-lift-subtle); + box-shadow: var(--hover-shadow-light); +} + +/* =================================================================== + NAVIGATION HOVER STYLES + =================================================================== */ + +/* Sidebar */ +.sidebar { + transition: var(--hover-transition-normal); +} + +.sidebar:hover { + box-shadow: var(--hover-shadow-heavy); +} + +/* Sidebar navigation items */ +.sidebar-nav li a { + transition: var(--hover-transition-fast); +} + +.sidebar-nav li a:hover { + background-color: var(--hover-bg-medium); + border-left-color: var(--primary-color); + color: var(--primary-color); +} + +/* Active state should be stronger */ +.sidebar-nav li.active a { + background-color: var(--hover-bg-strong); + border-left-color: var(--primary-gradient-end); + color: var(--primary-gradient-end); +} + +/* Top navigation */ +.navbar-nav .nav-link:hover { + color: var(--primary-color); + background-color: var(--hover-bg-light); + border-radius: 4px; +} + +/* Dropdown items */ +.dropdown-item:hover { + background-color: var(--hover-bg-medium); + color: var(--primary-color); +} + +/* =================================================================== + CARD & PANEL HOVER STYLES + =================================================================== */ + +/* Base card hover */ +.card, +.panel, +.dashboard-card, +.admin-card, +.stat-card { + transition: var(--hover-transition-normal); +} + +.card:hover, +.panel:hover, +.dashboard-card:hover { + transform: var(--hover-lift-small); + box-shadow: var(--hover-shadow-medium); +} + +/* Clickable cards get stronger effect */ +.admin-card:hover, +.clickable-card:hover { + transform: var(--hover-lift-medium); + box-shadow: var(--hover-shadow-strong); + cursor: pointer; +} + +/* Stat cards */ +.stat-card:hover { + transform: var(--hover-lift-small); + box-shadow: var(--hover-shadow-medium); + border-color: var(--hover-bg-strong); +} + +/* Management cards */ +.management-card { + transition: var(--hover-transition-normal); +} + +.management-card:hover { + transform: var(--hover-lift-small); + box-shadow: var(--hover-shadow-medium); + border-color: var(--hover-bg-medium); +} + +/* =================================================================== + TABLE HOVER STYLES + =================================================================== */ + +/* Table rows */ +.table tbody tr, +.data-table tbody tr, +.time-history tbody tr { + transition: var(--hover-transition-fast); +} + +.table tbody tr:hover, +.data-table tbody tr:hover, +.time-history tbody tr:hover { + background-color: var(--hover-bg-light); +} + +/* Clickable rows get pointer */ +.clickable-row:hover { + cursor: pointer; + background-color: var(--hover-bg-medium); +} + +/* =================================================================== + FORM ELEMENT HOVER STYLES + =================================================================== */ + +/* Input fields */ +.form-control, +.form-select { + transition: var(--hover-transition-fast); +} + +.form-control:hover:not(:focus), +.form-select:hover:not(:focus) { + border-color: var(--primary-color); + box-shadow: 0 0 0 1px var(--hover-bg-light); +} + +/* Checkboxes and radios */ +.form-check-input:hover { + border-color: var(--primary-color); + box-shadow: 0 0 0 2px var(--hover-bg-medium); +} + +/* =================================================================== + ICON HOVER STYLES + =================================================================== */ + +/* Icon buttons */ +.icon-btn { + transition: var(--hover-transition-fast); +} + +.icon-btn:hover { + color: var(--primary-color); + background-color: var(--hover-bg-medium); + border-radius: 50%; +} + +/* Action icons */ +.action-icon:hover { + color: var(--primary-color); + transform: scale(1.1); +} + +/* =================================================================== + SPECIAL COMPONENT HOVER STYLES + =================================================================== */ + +/* Task cards */ +.task-card { + transition: var(--hover-transition-normal); +} + +.task-card:hover { + transform: var(--hover-lift-subtle); + box-shadow: var(--hover-shadow-medium); + border-left-color: var(--primary-color); +} + +/* Note cards */ +.note-card:hover { + transform: var(--hover-lift-small); + box-shadow: var(--hover-shadow-medium); + border-color: var(--hover-bg-strong); +} + +/* Export sections */ +.export-section:hover { + transform: var(--hover-lift-small); + box-shadow: var(--hover-shadow-strong); +} + +/* Widget hover */ +.widget:hover { + transform: var(--hover-lift-small); + box-shadow: var(--hover-shadow-medium); + border-color: var(--hover-bg-medium); +} + +/* Mode buttons */ +.mode-btn:hover:not(.active) { + background-color: var(--hover-bg-medium); + color: var(--primary-color); + border-color: var(--primary-color); +} + +/* Tab buttons */ +.tab-btn:hover:not(.active) { + background-color: var(--hover-bg-light); + color: var(--primary-color); + border-bottom-color: var(--primary-color); +} + +/* =================================================================== + UTILITY HOVER CLASSES + =================================================================== */ + +/* Add these classes to elements for consistent hover effects */ +.hover-lift:hover { + transform: var(--hover-lift-small); +} + +.hover-lift-large:hover { + transform: var(--hover-lift-large); +} + +.hover-shadow:hover { + box-shadow: var(--hover-shadow-medium); +} + +.hover-shadow-strong:hover { + box-shadow: var(--hover-shadow-strong); +} + +.hover-bg:hover { + background-color: var(--hover-bg-medium); +} + +.hover-primary:hover { + color: var(--primary-color); +} + +.hover-scale:hover { + transform: scale(1.05); +} + +.hover-brightness:hover { + filter: brightness(1.1); +} + +/* =================================================================== + ANIMATION UTILITIES + =================================================================== */ + +/* Smooth all transitions */ +.smooth-transition { + transition: var(--hover-transition-smooth); +} + +/* Quick transitions for responsive feel */ +.quick-transition { + transition: var(--hover-transition-fast); +} + +/* Disable transitions on request */ +.no-transition { + transition: none !important; +} + +/* =================================================================== + DARK MODE ADJUSTMENTS (if applicable) + =================================================================== */ + +@media (prefers-color-scheme: dark) { + :root { + --hover-bg-light: rgba(102, 126, 234, 0.1); + --hover-bg-medium: rgba(102, 126, 234, 0.2); + --hover-bg-strong: rgba(102, 126, 234, 0.3); + } +} \ No newline at end of file diff --git a/static/css/splash.css b/static/css/splash.css index d4da092..5531572 100644 --- a/static/css/splash.css +++ b/static/css/splash.css @@ -64,15 +64,15 @@ } .btn-primary { - background: #4CAF50; + background: #667eea; color: white; box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3); } .btn-primary:hover { - background: #45a049; + background: linear-gradient(135deg, #5569d6 0%, #6a4195 100%); transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); } .btn-secondary { @@ -83,7 +83,9 @@ .btn-secondary:hover { background: white; - color: #2a5298; + color: #667eea; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2); } /* Floating Clock Animation */ @@ -135,7 +137,7 @@ width: 2px; height: 110px; margin-left: -1px; - background: #4CAF50; + background: #667eea; animation: rotate 60s linear infinite; } @@ -171,8 +173,8 @@ } .feature-card:hover { - transform: translateY(-5px); - box-shadow: 0 10px 30px rgba(0,0,0,0.12); + transform: translateY(-3px); + box-shadow: 0 10px 30px rgba(102, 126, 234, 0.2); } .feature-icon { @@ -329,7 +331,7 @@ top: -15px; left: 50%; transform: translateX(-50%); - background: #4CAF50; + background: #667eea; color: white; padding: 0.5rem 1.5rem; border-radius: 20px; @@ -375,7 +377,7 @@ .btn-pricing { display: inline-block; padding: 1rem 2rem; - background: #4CAF50; + background: #667eea; color: white; text-decoration: none; border-radius: 6px; @@ -385,8 +387,9 @@ } .btn-pricing:hover { - background: #45a049; + background: linear-gradient(135deg, #5569d6 0%, #6a4195 100%); transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.2); } .pricing-card.featured .btn-pricing { @@ -394,7 +397,8 @@ } .pricing-card.featured .btn-pricing:hover { - background: #1e3c72; + background: linear-gradient(135deg, #5569d6 0%, #6a4195 100%); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); } /* Final CTA */ diff --git a/static/css/style.css b/static/css/style.css index 77e34a8..730f740 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -112,7 +112,7 @@ button { } .sidebar:hover { - box-shadow: 0 6px 30px rgba(0,0,0,0.12); + box-shadow: 0 6px 30px rgba(102, 126, 234, 0.15); } /* Custom scrollbar for sidebar */ @@ -169,7 +169,7 @@ button { } .sidebar-header h2 a:hover { - color: #4CAF50; + color: #667eea; } .sidebar-header small { @@ -229,14 +229,14 @@ button { .sidebar-nav li a:hover { background-color: #e9ecef; - border-left-color: #4CAF50; + border-left-color: #667eea; color: #333; } .sidebar-nav li a.active { - background-color: #e8f5e9; - border-left-color: #4CAF50; - color: #2e7d32; + background-color: rgba(102, 126, 234, 0.1); + border-left-color: #667eea; + color: #5569d6; font-weight: 600; } @@ -366,7 +366,7 @@ body:has(.sidebar.collapsed) .main-content { } .feature h3 { - color: #4CAF50; + color: #667eea; margin-top: 0; } @@ -457,8 +457,10 @@ button { } .btn-primary:hover { - background-color: #0056b3; - border-color: #0056b3; + background-color: #5569d6; + border-color: #5569d6; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2); } .btn-secondary { @@ -470,6 +472,8 @@ button { .btn-secondary:hover { background-color: #545b62; border-color: #545b62; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15); } .btn-success { @@ -481,6 +485,8 @@ button { .btn-success:hover { background-color: #218838; border-color: #218838; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15); } .btn-danger { @@ -492,6 +498,8 @@ button { .btn-danger:hover { background-color: #c82333; border-color: #c82333; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15); } .btn-warning { @@ -503,6 +511,8 @@ button { .btn-warning:hover { background-color: #e0a800; border-color: #e0a800; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15); } .btn-info { @@ -514,6 +524,8 @@ button { .btn-info:hover { background-color: #138496; border-color: #138496; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15); } /* Button Outline Variants */ @@ -524,8 +536,11 @@ button { } .btn-outline:hover { - background-color: #007bff; + background-color: #667eea; color: white; + border-color: #667eea; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2); } /* Special Button Styles */ @@ -539,7 +554,9 @@ button { .btn-filter:hover { background-color: #f8f9fa; - border-color: #adb5bd; + border-color: #667eea; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(102, 126, 234, 0.15); } .btn-filter.active { @@ -551,7 +568,7 @@ button { /* Generic Button Hover */ .btn:hover { transform: translateY(-1px); - box-shadow: 0 2px 4px rgba(0,0,0,0.1); + box-shadow: 0 2px 4px rgba(102, 126, 234, 0.15); } footer { @@ -600,7 +617,7 @@ footer { } .footer-links a:hover { - color: var(--primary-color); + color: #667eea; } .footer-separator { @@ -662,8 +679,8 @@ footer { } .email-nag-dismiss:hover { - background-color: rgba(0,0,0,0.1); - color: #333; + background-color: rgba(102, 126, 234, 0.1); + color: #5569d6; } @keyframes slideDown { @@ -748,7 +765,9 @@ body:has(.sidebar.collapsed) footer { } .arrive-btn:hover { - background-color: #45a049; + background-color: #667eea; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2); } .leave-btn { @@ -760,6 +779,8 @@ body:has(.sidebar.collapsed) footer { .leave-btn:hover { background-color: #d32f2f; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15); } .time-history { @@ -788,7 +809,7 @@ body:has(.sidebar.collapsed) footer { } .time-history tr:hover { - background-color: #f5f5f5; + background-color: rgba(102, 126, 234, 0.05); } .button-group { @@ -807,6 +828,8 @@ body:has(.sidebar.collapsed) footer { .pause-btn:hover { background-color: #f57c00; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15); } .resume-btn { @@ -817,7 +840,9 @@ body:has(.sidebar.collapsed) footer { } .resume-btn:hover { - background-color: #1976D2; + background-color: #667eea; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2); } .break-info { @@ -971,7 +996,9 @@ body:has(.sidebar.collapsed) footer { } .edit-entry-btn:hover { - background-color: #0b7dda; + background-color: #667eea; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(102, 126, 234, 0.2); } .delete-entry-btn { @@ -981,6 +1008,8 @@ body:has(.sidebar.collapsed) footer { .delete-entry-btn:hover { background-color: #d32f2f; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(102, 126, 234, 0.15); } input[type="date"], input[type="time"] { @@ -1037,8 +1066,8 @@ input[type="time"]::-webkit-datetime-edit { } .admin-card:hover { - transform: translateY(-5px); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); + transform: translateY(-3px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.15); } .admin-card h2 { @@ -1113,11 +1142,11 @@ input[type="time"]::-webkit-datetime-edit { } .checkbox-container:hover input ~ .checkmark { - background-color: #ccc; + background-color: rgba(102, 126, 234, 0.15); } .checkbox-container input:checked ~ .checkmark { - background-color: #2196F3; + background-color: #667eea; } .checkmark:after { @@ -1171,7 +1200,7 @@ input[type="time"]::-webkit-datetime-edit { } .data-table tr:hover { - background-color: #f5f5f5; + background-color: rgba(102, 126, 234, 0.05); } /* Team Hours Page Styles */ @@ -1266,16 +1295,16 @@ input[type="time"]::-webkit-datetime-edit { .export-section:hover { transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.2); } .export-section h3 { - color: #4CAF50; + color: #667eea; margin-top: 0; margin-bottom: 1.5rem; font-size: 1.3rem; font-weight: 600; - border-bottom: 2px solid #4CAF50; + border-bottom: 2px solid #667eea; padding-bottom: 0.5rem; } @@ -1296,7 +1325,7 @@ input[type="time"]::-webkit-datetime-edit { .quick-export-buttons .btn:hover { transform: translateY(-1px); - box-shadow: 0 3px 10px rgba(76, 175, 80, 0.3); + box-shadow: 0 3px 10px rgba(102, 126, 234, 0.25); } .export-button-container { @@ -1305,7 +1334,7 @@ input[type="time"]::-webkit-datetime-edit { } .export-button-container .btn { - background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 1rem 2rem; font-size: 1.1rem; @@ -1314,13 +1343,13 @@ input[type="time"]::-webkit-datetime-edit { display: inline-block; transition: all 0.2s ease; font-weight: 600; - box-shadow: 0 2px 10px rgba(76, 175, 80, 0.3); + box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3); } .export-button-container .btn:hover { transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(76, 175, 80, 0.4); - background: linear-gradient(135deg, #45a049 0%, #4CAF50 100%); + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.25); + background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); } /* Custom date range form styling */ @@ -1348,8 +1377,8 @@ input[type="time"]::-webkit-datetime-edit { .export-section .form-group input:focus, .export-section .form-group select:focus { outline: none; - border-color: #4CAF50; - box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1); + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } /* Team Hours Export Styling */ @@ -1364,7 +1393,7 @@ input[type="time"]::-webkit-datetime-edit { } #export-buttons h4 { - color: #4CAF50; + color: #667eea; margin-bottom: 1rem; font-size: 1.2rem; font-weight: 600; @@ -1387,7 +1416,7 @@ input[type="time"]::-webkit-datetime-edit { #export-buttons .quick-export-buttons .btn:hover { transform: translateY(-1px); - box-shadow: 0 3px 10px rgba(76, 175, 80, 0.3); + box-shadow: 0 3px 10px rgba(102, 126, 234, 0.25); } /* Responsive Design for Sidebar Navigation */ @media (max-width: 1024px) { @@ -1528,14 +1557,14 @@ input[type="time"]::-webkit-datetime-edit { } .mode-btn.active { - background: #4CAF50; + background: #667eea; color: white; - box-shadow: 0 2px 4px rgba(76, 175, 80, 0.3); + box-shadow: 0 2px 4px rgba(102, 126, 234, 0.3); } .mode-btn:hover:not(.active) { - background: #e9ecef; - color: #495057; + background: rgba(102, 126, 234, 0.1); + color: #5569d6; } .filter-panel { @@ -1579,8 +1608,8 @@ input[type="time"]::-webkit-datetime-edit { .filter-group input:focus, .filter-group select:focus { outline: none; - border-color: #4CAF50; - box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1); + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .view-tabs { @@ -1603,14 +1632,14 @@ input[type="time"]::-webkit-datetime-edit { } .tab-btn.active { - color: #4CAF50; - border-bottom-color: #4CAF50; - background: rgba(76, 175, 80, 0.05); + color: #667eea; + border-bottom-color: #667eea; + background: rgba(102, 126, 234, 0.05); } .tab-btn:hover:not(.active) { - color: #495057; - background: rgba(0, 0, 0, 0.05); + color: #5569d6; + background: rgba(102, 126, 234, 0.05); } .view-content { @@ -1700,7 +1729,7 @@ input[type="time"]::-webkit-datetime-edit { border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); text-align: center; - border-left: 4px solid #4CAF50; + border-left: 4px solid #667eea; } .stat-card h4 { @@ -1796,8 +1825,8 @@ input[type="time"]::-webkit-datetime-edit { .form-group textarea:focus, .form-group select:focus { outline: none; - border-color: #4CAF50; - box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1); + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } /* Responsive Design for Analytics */ @@ -2031,7 +2060,7 @@ input[type="time"]::-webkit-datetime-edit { } .view-btn:hover:not(.active) { - background: #e9ecef; + background: rgba(102, 126, 234, 0.1); } /* Statistics Cards */ @@ -2165,7 +2194,7 @@ input[type="time"]::-webkit-datetime-edit { } .management-card:hover { - box-shadow: 0 4px 12px rgba(0,0,0,0.1); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15); transform: translateY(-2px); } @@ -2289,7 +2318,7 @@ input[type="time"]::-webkit-datetime-edit { } .card:hover { - box-shadow: 0 4px 12px rgba(0,0,0,0.15); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2); } .card-header { @@ -2362,7 +2391,7 @@ input[type="time"]::-webkit-datetime-edit { } .widget:hover { - box-shadow: 0 4px 8px rgba(0,0,0,0.15); + box-shadow: 0 4px 8px rgba(102, 126, 234, 0.2); } .widget-header { @@ -2549,8 +2578,8 @@ input[type="time"]::-webkit-datetime-edit { } .user-dropdown-toggle:hover { - background-color: #e9ecef; - color: #333; + background-color: rgba(102, 126, 234, 0.1); + color: #5569d6; } /* Removed nav-icon style as we're using avatar instead */ @@ -2643,7 +2672,7 @@ input[type="time"]::-webkit-datetime-edit { } .user-dropdown-menu a:hover { - background-color: #f0f0f0; + background-color: rgba(102, 126, 234, 0.1); } .user-dropdown-menu .nav-icon { diff --git a/static/css/time-tracking.css b/static/css/time-tracking.css new file mode 100644 index 0000000..14a042c --- /dev/null +++ b/static/css/time-tracking.css @@ -0,0 +1,770 @@ +/* Time Tracking Styles */ + +/* Container */ +.time-tracking-container { + max-width: 1400px; + margin: 0 auto; + padding: 2rem; +} + +/* Page Header - reuse existing styles */ +.page-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 16px; + padding: 2rem; + color: white; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + margin-bottom: 2rem; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 2rem; +} + +.page-title { + font-size: 2rem; + font-weight: 700; + margin: 0; + display: flex; + align-items: center; + gap: 0.75rem; +} + +.page-icon { + font-size: 2.5rem; + display: inline-block; + animation: float 3s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +.page-subtitle { + font-size: 1.1rem; + opacity: 0.9; + margin: 0.5rem 0 0 0; +} + +.header-actions { + display: flex; + gap: 1rem; +} + +/* Timer Section */ +.timer-section { + margin-bottom: 2rem; +} + +.timer-card { + background: white; + border-radius: 16px; + padding: 2rem; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); + border: 1px solid #e5e7eb; +} + +.timer-card.active { + border: 2px solid #10b981; + background: linear-gradient(135deg, #10b98110 0%, #059b6910 100%); +} + +.timer-display { + text-align: center; + margin-bottom: 2rem; +} + +.timer-value { + font-size: 4rem; + font-weight: 700; + color: #1f2937; + font-family: 'Monaco', 'Courier New', monospace; + letter-spacing: 0.1em; +} + +.timer-status { + margin-top: 0.5rem; +} + +.status-badge { + display: inline-block; + padding: 0.5rem 1rem; + border-radius: 20px; + font-size: 0.875rem; + font-weight: 600; + text-transform: uppercase; +} + +.status-badge.active { + background: #10b981; + color: white; +} + +.status-badge.paused { + background: #f59e0b; + color: white; +} + +.timer-info { + background: #f8f9fa; + border-radius: 12px; + padding: 1.5rem; + margin-bottom: 1.5rem; +} + +.info-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid #e5e7eb; +} + +.info-row:last-child { + border-bottom: none; +} + +.info-label { + font-weight: 600; + color: #6b7280; +} + +.info-value { + color: #1f2937; +} + +.project-badge { + display: inline-block; + padding: 0.25rem 0.75rem; + border-radius: 16px; + color: white; + font-size: 0.875rem; + font-weight: 500; +} + +.task-badge { + display: inline-block; + padding: 0.25rem 0.75rem; + background: #e5e7eb; + border-radius: 8px; + font-size: 0.875rem; +} + +.timer-actions { + display: flex; + gap: 1rem; + justify-content: center; +} + +/* Start Work Form */ +.start-work-container { + text-align: center; +} + +.start-work-container h2 { + font-size: 1.75rem; + color: #1f2937; + margin-bottom: 0.5rem; +} + +.start-work-container p { + color: #6b7280; + margin-bottom: 2rem; +} + +.modern-form { + max-width: 600px; + margin: 0 auto; +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.form-label { + font-weight: 600; + color: #374151; + font-size: 0.95rem; +} + +.optional-badge { + background: #e5e7eb; + color: #6b7280; + padding: 0.125rem 0.5rem; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 500; + margin-left: 0.5rem; +} + +.form-control { + padding: 0.75rem 1rem; + border: 2px solid #e5e7eb; + border-radius: 8px; + font-size: 1rem; + transition: all 0.2s ease; + background-color: #f9fafb; +} + +.form-control:focus { + outline: none; + border-color: #667eea; + background-color: white; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.form-actions { + margin-top: 1.5rem; +} + +/* Stats Section */ +.stats-section { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.stat-card { + background: white; + padding: 1.5rem; + border-radius: 12px; + text-align: center; + border: 1px solid #e5e7eb; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + transition: all 0.3s ease; +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); +} + +.stat-value { + font-size: 2rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: #667eea; +} + +.stat-label { + font-size: 0.9rem; + color: #6b7280; + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 600; +} + +/* Entries Section */ +.entries-section { + background: white; + border-radius: 16px; + padding: 2rem; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); + border: 1px solid #e5e7eb; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} + +.section-title { + font-size: 1.5rem; + font-weight: 600; + color: #1f2937; + margin: 0; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.view-toggle { + display: flex; + gap: 0.5rem; +} + +.toggle-btn { + padding: 0.5rem 1rem; + border: 2px solid #e5e7eb; + background: white; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 0.25rem; + font-weight: 500; +} + +.toggle-btn.active { + background: #667eea; + color: white; + border-color: #667eea; +} + +.view-container { + display: none; +} + +.view-container.active { + display: block; +} + +/* Table Styles */ +.entries-table-container { + overflow-x: auto; +} + +.entries-table { + width: 100%; + border-collapse: collapse; +} + +.entries-table th { + text-align: left; + padding: 1rem; + border-bottom: 2px solid #e5e7eb; + font-weight: 600; + color: #374151; + background: #f8f9fa; +} + +.entries-table td { + padding: 1rem; + border-bottom: 1px solid #e5e7eb; +} + +.entry-row:hover { + background: #f8f9fa; +} + +.date-cell { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.date-day { + font-size: 1.25rem; + font-weight: 700; + color: #1f2937; +} + +.date-month { + font-size: 0.875rem; + color: #6b7280; + text-transform: uppercase; +} + +.time-cell { + display: flex; + align-items: center; + gap: 0.5rem; + font-family: 'Monaco', 'Courier New', monospace; +} + +.time-separator { + color: #9ca3af; +} + +.project-task-cell { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.project-tag { + display: inline-block; + padding: 0.25rem 0.5rem; + border-radius: 4px; + color: white; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; +} + +.task-name, +.project-name { + color: #374151; +} + +.no-project { + color: #9ca3af; + font-style: italic; +} + +.duration-badge { + display: inline-block; + padding: 0.25rem 0.75rem; + background: #ede9fe; + color: #5b21b6; + border-radius: 16px; + font-weight: 600; + font-size: 0.875rem; +} + +.break-duration { + color: #6b7280; +} + +.notes-preview { + color: #6b7280; + font-size: 0.875rem; +} + +.actions-cell { + display: flex; + gap: 0.5rem; +} + +/* Specific button hover states */ +.btn-icon.resume-work-btn:hover:not(:disabled) { + color: #10b981; + border-color: #10b981; + background: #f0fdf4; +} + +.btn-icon.edit-entry-btn:hover { + color: #667eea; + border-color: #667eea; + background: #f3f4f6; +} + +.btn-icon.delete-entry-btn:hover { + color: #ef4444; + border-color: #ef4444; + background: #fef2f2; +} + +/* Grid View */ +.entries-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: 1.5rem; +} + +.entry-card { + background: #f8f9fa; + border-radius: 12px; + padding: 1.5rem; + border: 1px solid #e5e7eb; + transition: all 0.3s ease; +} + +.entry-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} + +.entry-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.entry-date { + font-weight: 600; + color: #374151; +} + +.entry-duration { + font-size: 1.25rem; + font-weight: 700; + color: #667eea; +} + +.entry-body { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.entry-project { + padding-left: 1rem; + border-left: 4px solid; +} + +.entry-task, +.entry-time, +.entry-notes { + display: flex; + align-items: flex-start; + gap: 0.5rem; + color: #6b7280; + font-size: 0.875rem; +} + +.entry-footer { + display: flex; + gap: 0.5rem; + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid #e5e7eb; +} + +/* Buttons */ +.btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 8px; + font-weight: 600; + font-size: 1rem; + cursor: pointer; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 0.5rem; + text-decoration: none; +} + +.btn-large { + padding: 1rem 2rem; + font-size: 1.125rem; +} + +.btn-sm { + padding: 0.5rem 1rem; + font-size: 0.875rem; +} + +.btn-icon { + padding: 0.5rem; + border: 1px solid #e5e7eb; + background: white; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + color: #6b7280; + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; +} + +.btn-icon:hover { + background: #f3f4f6; + transform: translateY(-2px); + color: #374151; +} + +.btn-icon:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-icon:disabled:hover { + transform: none; + background: white; + color: #6b7280; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); +} + +.btn-secondary { + background: white; + color: #4b5563; + border: 2px solid #e5e7eb; +} + +.btn-secondary:hover { + background: #f3f4f6; +} + +.btn-success { + background: #10b981; + color: white; +} + +.btn-success:hover { + background: #059669; + transform: translateY(-2px); +} + +.btn-warning { + background: #f59e0b; + color: white; +} + +.btn-warning:hover { + background: #d97706; + transform: translateY(-2px); +} + +.btn-danger { + background: #ef4444; + color: white; +} + +.btn-danger:hover { + background: #dc2626; + transform: translateY(-2px); +} + +.btn-ghost { + background: transparent; + color: #6b7280; +} + +.btn-ghost:hover { + color: #374151; + background: #f3f4f6; +} + +/* Empty State */ +.empty-state { + text-align: center; + padding: 4rem 2rem; +} + +.empty-icon { + font-size: 4rem; + margin-bottom: 1rem; +} + +.empty-state h3 { + font-size: 1.5rem; + font-weight: 600; + color: #1f2937; + margin-bottom: 0.5rem; +} + +.empty-state p { + color: #6b7280; +} + +/* Modal Styles */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1000; +} + +.modal-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); +} + +.modal-content { + position: relative; + background: white; + border-radius: 16px; + max-width: 600px; + margin: 5vh auto; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.2); +} + +.modal-small { + max-width: 400px; +} + +.modal-header { + padding: 2rem; + border-bottom: 1px solid #e5e7eb; + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-title { + font-size: 1.5rem; + font-weight: 600; + color: #1f2937; + margin: 0; +} + +.modal-close { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: #6b7280; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + transition: all 0.2s ease; +} + +.modal-close:hover { + background: #f3f4f6; + color: #1f2937; +} + +.modal-body { + padding: 2rem; +} + +.modal-footer { + padding: 1.5rem 2rem; + border-top: 1px solid #e5e7eb; + display: flex; + justify-content: flex-end; + gap: 1rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .time-tracking-container { + padding: 1rem; + } + + .form-row { + grid-template-columns: 1fr; + } + + .timer-value { + font-size: 3rem; + } + + .entries-grid { + grid-template-columns: 1fr; + } + + .entries-table { + font-size: 0.875rem; + } + + .entries-table th, + .entries-table td { + padding: 0.5rem; + } +} \ No newline at end of file diff --git a/templates/_time_tracking_interface.html b/templates/_time_tracking_interface.html new file mode 100644 index 0000000..b6f6eec --- /dev/null +++ b/templates/_time_tracking_interface.html @@ -0,0 +1,356 @@ + +
    + + + + +
    + {% if active_entry %} + +
    +
    +
    + 00:00:00 +
    +
    + {% if active_entry.is_paused %} + On Break + {% else %} + Working + {% endif %} +
    +
    + +
    +
    + Started: + {{ active_entry.arrival_time|format_datetime }} +
    + + {% if active_entry.project %} +
    + Project: + + {{ active_entry.project.code }} - {{ active_entry.project.name }} + +
    + {% endif %} + + {% if active_entry.task %} +
    + Task: + + {{ active_entry.task.title }} + +
    + {% endif %} + + {% if active_entry.notes %} +
    + Notes: + {{ active_entry.notes }} +
    + {% endif %} + + {% if active_entry.is_paused %} +
    + Break started: + {{ active_entry.pause_start_time|format_time }} +
    + {% endif %} + + {% if active_entry.total_break_duration > 0 %} +
    + Total breaks: + {{ active_entry.total_break_duration|format_duration }} +
    + {% endif %} +
    + +
    + + +
    +
    + {% else %} + +
    +
    +

    Start Tracking Time

    +

    Select a project and task to begin tracking your work

    + +
    +
    +
    + + +
    + +
    + + +
    +
    + +
    + + +
    + +
    + +
    + +
    +
    + {% endif %} +
    + + +
    +
    +
    {{ today_hours|format_duration }}
    +
    Today
    +
    +
    +
    {{ week_hours|format_duration }}
    +
    This Week
    +
    +
    +
    {{ month_hours|format_duration }}
    +
    This Month
    +
    +
    +
    {{ active_projects|length }}
    +
    Active Projects
    +
    +
    + + +
    +
    +

    + + Recent Time Entries +

    +
    + + +
    +
    + + +
    + {% if history %} +
    +
    {{ entry.arrival_time|format_time }} - + {{ entry.departure_time|format_time if entry.departure_time else 'Active' }}
    + + + + + + + + + + + + + {% for entry in history %} + + + + + + + + + + {% endfor %} + +
    DateTimeProject / TaskDurationBreakNotesActions
    +
    + {{ entry.arrival_time.strftime('%d') }} + {{ entry.arrival_time.strftime('%b') }} +
    +
    +
    + {{ entry.arrival_time|format_time }} + + {{ entry.departure_time|format_time if entry.departure_time else 'Active' }} +
    +
    +
    + {% if entry.project %} + + {{ entry.project.code }} + + {% endif %} + {% if entry.task %} + {{ entry.task.title }} + {% elif entry.project %} + {{ entry.project.name }} + {% else %} + No project + {% endif %} +
    +
    + + {{ entry.duration|format_duration if entry.duration is not none else 'In progress' }} + + + + {{ entry.total_break_duration|format_duration if entry.total_break_duration else '-' }} + + + + {{ entry.notes[:30] + '...' if entry.notes and entry.notes|length > 30 else entry.notes or '-' }} + + +
    + {% if entry.departure_time and not active_entry %} + {% if entry.arrival_time.date() >= today %} + + {% else %} + + {% endif %} + {% endif %} + + +
    +
    +
    + {% else %} +
    +
    +

    No time entries yet

    +

    Start tracking your time to see entries here

    +
    + {% endif %} +
    + + +
    +
    + {% for entry in history %} +
    +
    + +
    + {{ entry.duration|format_duration if entry.duration is not none else 'Active' }} +
    +
    + +
    + {% if entry.project %} +
    + {{ entry.project.code }} - {{ entry.project.name }} +
    + {% endif %} + + {% if entry.task %} +
    + {{ entry.task.title }} +
    + {% endif %} + +
    + + {{ entry.arrival_time|format_time }} - {{ entry.departure_time|format_time if entry.departure_time else 'Active' }} +
    + + {% if entry.notes %} +
    + + {{ entry.notes }} +
    + {% endif %} +
    + + +
    + {% endfor %} +
    +
    +
    +
    + + +{% include '_time_tracking_modals.html' %} + + + + \ No newline at end of file diff --git a/templates/_time_tracking_modals.html b/templates/_time_tracking_modals.html new file mode 100644 index 0000000..5d157d5 --- /dev/null +++ b/templates/_time_tracking_modals.html @@ -0,0 +1,146 @@ + + + + + + + + + + \ No newline at end of file diff --git a/templates/admin_users.html b/templates/admin_users.html index 25279a1..abab77a 100644 --- a/templates/admin_users.html +++ b/templates/admin_users.html @@ -7,7 +7,7 @@

    - 👤 + User Management

    Manage user accounts and permissions across your organization

    @@ -49,7 +49,7 @@

    - 🔍 + {{ user.username }}

    @@ -174,22 +174,22 @@

    {{ user.username }}

    - ✏️ + {% if user.id != g.user.id %}
    {% if user.is_blocked %} {% else %} {% endif %}
    {% endif %}
    @@ -203,14 +203,14 @@

    {{ user.username }}

    {% else %}
    -
    🏃‍♂️
    +

    Sprint Management

    Organize work into sprints with agile project tracking

    @@ -51,12 +51,12 @@

    Team Collaboration

    Manage teams, projects, and resources all in one place

    -
    🔒
    +

    Enterprise Security

    Bank-level encryption with role-based access control

    -
    🌐
    +

    Multi-Company Support

    Perfect for agencies managing multiple client accounts

    @@ -89,17 +89,17 @@

    Why Choose {{ g.branding.app_name if g.branding else '

    Get Started in Minutes

    -
    1️⃣
    +

    Sign Up

    Create your free account in seconds. No credit card required.

    -
    2️⃣
    +

    Set Up Your Workspace

    Add your company, teams, and projects to organize your time tracking.

    -
    3️⃣
    +

    Start Tracking

    Click "Arrive" to start tracking, "Leave" when done. It's that simple!

    @@ -145,1234 +145,8 @@

    Ready to Take Control of Your Time?

    {% else %} - -
    - - - - -
    - {% if active_entry %} - -
    -
    -
    - 00:00:00 -
    -
    - {% if active_entry.is_paused %} - On Break - {% else %} - Working - {% endif %} -
    -
    - -
    -
    - Started: - {{ active_entry.arrival_time|format_datetime }} -
    - - {% if active_entry.project %} -
    - Project: - - {{ active_entry.project.code }} - {{ active_entry.project.name }} - -
    - {% endif %} - - {% if active_entry.task %} -
    - Task: - - {{ active_entry.task.title }} - -
    - {% endif %} - - {% if active_entry.notes %} -
    - Notes: - {{ active_entry.notes }} -
    - {% endif %} - - {% if active_entry.is_paused %} -
    - Break started: - {{ active_entry.pause_start_time|format_time }} -
    - {% endif %} - - {% if active_entry.total_break_duration > 0 %} -
    - Total breaks: - {{ active_entry.total_break_duration|format_duration }} -
    - {% endif %} -
    - -
    - - -
    -
    - {% else %} - -
    -
    -

    Start Tracking Time

    -

    Select a project and task to begin tracking your work

    - -
    -
    -
    - - -
    - -
    - - -
    -
    - -
    - - -
    - -
    - -
    -
    -
    -
    - {% endif %} -
    - - - {% if today_hours is defined %} -
    -
    -
    {{ today_hours|format_duration }}
    -
    Today
    -
    -
    -
    {{ week_hours|format_duration }}
    -
    This Week
    -
    -
    -
    {{ month_hours|format_duration }}
    -
    This Month
    -
    -
    -
    {{ active_projects|length if active_projects else 0 }}
    -
    Active Projects
    -
    -
    - {% endif %} - - -
    -
    -

    - 📋 - Recent Time Entries -

    -
    - - -
    -
    - - -
    - {% if history %} -
    - - - - - - - - - - - - - - {% for entry in history %} - - - - - - - - - - {% endfor %} - -
    DateTimeProject / TaskDurationBreakNotesActions
    -
    - {{ entry.arrival_time.strftime('%d') }} - {{ entry.arrival_time.strftime('%b') }} -
    -
    -
    - {{ entry.arrival_time|format_time }} - - {{ entry.departure_time|format_time if entry.departure_time else 'Active' }} -
    -
    -
    - {% if entry.project %} - - {{ entry.project.code }} - - {% endif %} - {% if entry.task %} - {{ entry.task.title }} - {% elif entry.project %} - {{ entry.project.name }} - {% else %} - No project - {% endif %} -
    -
    - - {{ entry.duration|format_duration if entry.duration is not none else 'In progress' }} - - - - {{ entry.total_break_duration|format_duration if entry.total_break_duration else '-' }} - - - - {{ entry.notes[:30] + '...' if entry.notes and entry.notes|length > 30 else entry.notes or '-' }} - - -
    - {% if entry.departure_time and not active_entry %} - - {% endif %} - - -
    -
    -
    - {% else %} -
    -
    📭
    -

    No time entries yet

    -

    Start tracking your time to see entries here

    -
    - {% endif %} -
    - - -
    -
    - {% for entry in history %} -
    -
    - -
    - {{ entry.duration|format_duration if entry.duration is not none else 'Active' }} -
    -
    - -
    - {% if entry.project %} -
    - {{ entry.project.code }} - {{ entry.project.name }} -
    - {% endif %} - - {% if entry.task %} -
    - 📋 {{ entry.task.title }} -
    - {% endif %} - -
    - 🕐 - {{ entry.arrival_time|format_time }} - {{ entry.departure_time|format_time if entry.departure_time else 'Active' }} -
    - - {% if entry.notes %} -
    - 📝 - {{ entry.notes }} -
    - {% endif %} -
    - - -
    - {% endfor %} -
    -
    -
    -
    - - - - - - - - - - - - - - - + +{% include '_time_tracking_interface.html' %} {% endif %} diff --git a/templates/invitations/list.html b/templates/invitations/list.html index 1b3e30c..3dafc19 100644 --- a/templates/invitations/list.html +++ b/templates/invitations/list.html @@ -7,7 +7,7 @@

    - 📨 + Invitations

    Manage team invitations for {{ g.user.company.name }}

    @@ -45,7 +45,7 @@

    {% if pending_invitations %}

    - + Pending Invitations

    @@ -56,15 +56,15 @@

    {{ invitation.email }}

    - 👤 + Role: {{ invitation.role }} - 📅 + Sent {{ invitation.created_at.strftime('%b %d, %Y') }} - + Expires {{ invitation.expires_at.strftime('%b %d, %Y') }}
    @@ -72,13 +72,13 @@

    {{ invitation.email }}

    @@ -97,7 +97,7 @@

    {{ invitation.email }}

    {% if accepted_invitations %}

    - + Accepted Invitations

    @@ -108,18 +108,18 @@

    {{ invitation.email }}

    - 👤 + Joined as: {{ invitation.accepted_by.username }} ({{ invitation.role }}) - 📅 + Accepted {{ invitation.accepted_at.strftime('%b %d, %Y') }}
    @@ -137,7 +137,7 @@

    {{ invitation.email }}

    {% if expired_invitations %}

    - ⏱️ + Expired Invitations

    @@ -148,11 +148,11 @@

    {{ invitation.email }}

    - 👤 + Role: {{ invitation.role }} - 📅 + Expired {{ invitation.expires_at.strftime('%b %d, %Y') }}
    @@ -160,7 +160,7 @@

    {{ invitation.email }}

    @@ -178,7 +178,7 @@

    {{ invitation.email }}

    {% if not pending_invitations and not accepted_invitations and not expired_invitations %}
    -
    📨
    +

    No invitations yet

    Start building your team by sending invitations

    diff --git a/templates/invitations/send.html b/templates/invitations/send.html index 4b5aab3..ead2ad1 100644 --- a/templates/invitations/send.html +++ b/templates/invitations/send.html @@ -7,7 +7,7 @@

    - ✉️ + Send Invitation

    Invite team members to join {{ g.user.company.name }}

    @@ -60,7 +60,7 @@

    - 📧 +

    What happens next?

    Enable Imprint Page - + When enabled, an "Imprint" link will appear in the footer linking to your custom legal page.
    @@ -158,7 +173,7 @@

    ⚖️ Imprint / Legal Page

    value="{{ branding.imprint_title or 'Imprint' }}" class="form-control" placeholder="Imprint"> - + The title that will be displayed on the imprint page.
    @@ -169,7 +184,7 @@

    ⚖️ Imprint / Legal Page

    class="form-control content-editor" rows="15" placeholder="Enter your imprint/legal information here...">{{ branding.imprint_content or '' }} - + You can use HTML to format your content. Common tags: <h2>, <h3>, <p>, <strong>, <br>, <a href="">
    @@ -178,7 +193,10 @@

    ⚖️ Imprint / Legal Page

    - + Cancel
    @@ -187,8 +205,99 @@

    ⚖️ Imprint / Legal Page