diff --git a/CTFd/forms/teams.py b/CTFd/forms/teams.py index e06da7c9..80ccb27e 100644 --- a/CTFd/forms/teams.py +++ b/CTFd/forms/teams.py @@ -4,45 +4,142 @@ from wtforms.validators import InputRequired from CTFd.forms import BaseForm from CTFd.forms.fields import SubmitField +from CTFd.models import TeamFieldEntries, TeamFields from CTFd.utils.countries import SELECT_COUNTRIES_LIST +def build_custom_team_fields( + form_cls, + include_entries=False, + fields_kwargs=None, + field_entries_kwargs=None, + blacklisted_items=("affiliation", "website"), +): + if fields_kwargs is None: + fields_kwargs = {} + if field_entries_kwargs is None: + field_entries_kwargs = {} + + fields = [] + new_fields = TeamFields.query.filter_by(**fields_kwargs).all() + user_fields = {} + + # Only include preexisting values if asked + if include_entries is True: + for f in TeamFieldEntries.query.filter_by(**field_entries_kwargs).all(): + user_fields[f.field_id] = f.value + + for field in new_fields: + if field.name.lower() in blacklisted_items: + continue + + form_field = getattr(form_cls, f"fields[{field.id}]") + + # Add the field_type to the field so we know how to render it + form_field.field_type = field.field_type + + # Only include preexisting values if asked + if include_entries is True: + initial = user_fields.get(field.id, "") + form_field.data = initial + if form_field.render_kw: + form_field.render_kw["data-initial"] = initial + else: + form_field.render_kw = {"data-initial": initial} + + fields.append(form_field) + return fields + + +def attach_custom_team_fields(form_cls, **kwargs): + new_fields = TeamFields.query.filter_by(**kwargs).all() + for field in new_fields: + validators = [] + if field.required: + validators.append(InputRequired()) + + if field.field_type == "text": + input_field = StringField( + field.name, description=field.description, validators=validators + ) + elif field.field_type == "boolean": + input_field = BooleanField( + field.name, description=field.description, validators=validators + ) + + setattr(form_cls, f"fields[{field.id}]", input_field) + + class TeamJoinForm(BaseForm): name = StringField("Team Name", validators=[InputRequired()]) password = PasswordField("Team Password", validators=[InputRequired()]) submit = SubmitField("Join") -class TeamRegisterForm(BaseForm): - name = StringField("Team Name", validators=[InputRequired()]) - password = PasswordField("Team Password", validators=[InputRequired()]) - submit = SubmitField("Create") +def TeamRegisterForm(*args, **kwargs): + class _TeamRegisterForm(BaseForm): + name = StringField("Team Name", validators=[InputRequired()]) + password = PasswordField("Team Password", validators=[InputRequired()]) + submit = SubmitField("Create") + + @property + def extra(self): + return build_custom_team_fields( + self, include_entries=False, blacklisted_items=() + ) + + attach_custom_team_fields(_TeamRegisterForm) + return _TeamRegisterForm(*args, **kwargs) -class TeamSettingsForm(BaseForm): - name = StringField( - "Team Name", description="Your team's public name shown to other competitors" - ) - password = PasswordField( - "New Team Password", description="Set a new team join password" - ) - confirm = PasswordField( - "Confirm Password", - description="Provide your current team password (or your password) to update your team's password", - ) - affiliation = StringField( - "Affiliation", - description="Your team's affiliation publicly shown to other competitors", - ) - website = URLField( - "Website", description="Your team's website publicly shown to other competitors" - ) - country = SelectField( - "Country", - choices=SELECT_COUNTRIES_LIST, - description="Your team's country publicly shown to other competitors", - ) - submit = SubmitField("Submit") +def TeamSettingsForm(*args, **kwargs): + class _TeamSettingsForm(BaseForm): + name = StringField( + "Team Name", + description="Your team's public name shown to other competitors", + ) + password = PasswordField( + "New Team Password", description="Set a new team join password" + ) + confirm = PasswordField( + "Confirm Password", + description="Provide your current team password (or your password) to update your team's password", + ) + affiliation = StringField( + "Affiliation", + description="Your team's affiliation publicly shown to other competitors", + ) + website = URLField( + "Website", + description="Your team's website publicly shown to other competitors", + ) + country = SelectField( + "Country", + choices=SELECT_COUNTRIES_LIST, + description="Your team's country publicly shown to other competitors", + ) + submit = SubmitField("Submit") + + @property + def extra(self): + return build_custom_team_fields( + self, + include_entries=True, + fields_kwargs={"editable": True}, + field_entries_kwargs={"team_id": self.obj.id}, + ) + + def __init__(self, *args, **kwargs): + """ + Custom init to persist the obj parameter to the rest of the form + """ + super().__init__(*args, **kwargs) + obj = kwargs.get("obj") + if obj: + self.obj = obj + + attach_custom_team_fields(_TeamSettingsForm) + return _TeamSettingsForm(*args, **kwargs) class TeamCaptainForm(BaseForm): @@ -82,7 +179,7 @@ class PublicTeamSearchForm(BaseForm): submit = SubmitField("Search") -class TeamCreateForm(BaseForm): +class TeamBaseForm(BaseForm): name = StringField("Team Name", validators=[InputRequired()]) email = EmailField("Email") password = PasswordField("Password") @@ -94,5 +191,41 @@ class TeamCreateForm(BaseForm): submit = SubmitField("Submit") -class TeamEditForm(TeamCreateForm): - pass +def TeamCreateForm(*args, **kwargs): + class _TeamCreateForm(TeamBaseForm): + pass + + @property + def extra(self): + return build_custom_team_fields(self, include_entries=False) + + attach_custom_team_fields(_TeamCreateForm) + + return _TeamCreateForm(*args, **kwargs) + + +def TeamEditForm(*args, **kwargs): + class _TeamEditForm(TeamBaseForm): + pass + + @property + def extra(self): + return build_custom_team_fields( + self, + include_entries=True, + fields_kwargs=None, + field_entries_kwargs={"team_id": self.obj.id}, + ) + + def __init__(self, *args, **kwargs): + """ + Custom init to persist the obj parameter to the rest of the form + """ + super().__init__(*args, **kwargs) + obj = kwargs.get("obj") + if obj: + self.obj = obj + + attach_custom_team_fields(_TeamEditForm) + + return _TeamEditForm(*args, **kwargs) diff --git a/CTFd/forms/users.py b/CTFd/forms/users.py index 55627171..4962aa22 100644 --- a/CTFd/forms/users.py +++ b/CTFd/forms/users.py @@ -76,9 +76,7 @@ def attach_custom_user_fields(form_cls, **kwargs): field.name, description=field.description, validators=validators ) - setattr( - form_cls, f"fields[{field.id}]", input_field, - ) + setattr(form_cls, f"fields[{field.id}]", input_field) class UserSearchForm(BaseForm): diff --git a/CTFd/models/__init__.py b/CTFd/models/__init__.py index e0979181..07949234 100644 --- a/CTFd/models/__init__.py +++ b/CTFd/models/__init__.py @@ -346,7 +346,9 @@ class Users(db.Model): if admin: return self.field_entries - return [entry for entry in self.field_entries if entry.field.public] + return [ + entry for entry in self.field_entries if entry.field.public and entry.value + ] def get_solves(self, admin=False): from CTFd.utils import get_config @@ -467,6 +469,10 @@ class Teams(db.Model): captain_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL")) captain = db.relationship("Users", foreign_keys=[captain_id]) + field_entries = db.relationship( + "TeamFieldEntries", foreign_keys="TeamFieldEntries.team_id", lazy="joined" + ) + created = db.Column(db.DateTime, default=datetime.datetime.utcnow) def __init__(self, **kwargs): @@ -478,6 +484,10 @@ class Teams(db.Model): return hash_password(str(plaintext)) + @property + def fields(self): + return self.get_fields(admin=False) + @property def solves(self): return self.get_solves(admin=False) @@ -503,6 +513,14 @@ class Teams(db.Model): else: return None + def get_fields(self, admin=False): + if admin: + return self.field_entries + + return [ + entry for entry in self.field_entries if entry.field.public and entry.value + ] + def get_solves(self, admin=False): from CTFd.utils import get_config diff --git a/CTFd/schemas/fields.py b/CTFd/schemas/fields.py index a6c8841a..0199b9d7 100644 --- a/CTFd/schemas/fields.py +++ b/CTFd/schemas/fields.py @@ -1,6 +1,6 @@ from marshmallow import fields -from CTFd.models import Fields, UserFieldEntries, db, ma +from CTFd.models import Fields, TeamFieldEntries, UserFieldEntries, db, ma class FieldSchema(ma.ModelSchema): @@ -22,3 +22,17 @@ class UserFieldEntriesSchema(ma.ModelSchema): name = fields.Nested(FieldSchema, only=("name"), attribute="field") description = fields.Nested(FieldSchema, only=("description"), attribute="field") type = fields.Nested(FieldSchema, only=("field_type"), attribute="field") + + +class TeamFieldEntriesSchema(ma.ModelSchema): + class Meta: + model = TeamFieldEntries + sqla_session = db.session + include_fk = True + load_only = ("id",) + exclude = ("field", "team", "team_id") + dump_only = ("team_id", "name", "description", "type") + + name = fields.Nested(FieldSchema, only=("name"), attribute="field") + description = fields.Nested(FieldSchema, only=("description"), attribute="field") + type = fields.Nested(FieldSchema, only=("field_type"), attribute="field") diff --git a/CTFd/schemas/teams.py b/CTFd/schemas/teams.py index 669a6ed0..68639c62 100644 --- a/CTFd/schemas/teams.py +++ b/CTFd/schemas/teams.py @@ -1,7 +1,10 @@ -from marshmallow import ValidationError, pre_load, validate +from marshmallow import ValidationError, post_dump, pre_load, validate +from marshmallow.fields import Nested from marshmallow_sqlalchemy import field_for +from sqlalchemy.orm import load_only -from CTFd.models import Teams, Users, ma +from CTFd.models import TeamFieldEntries, TeamFields, Teams, Users, ma +from CTFd.schemas.fields import TeamFieldEntriesSchema from CTFd.utils import get_config, string_types from CTFd.utils.crypto import verify_password from CTFd.utils.user import get_current_team, get_current_user, is_admin @@ -44,6 +47,9 @@ class TeamSchema(ma.ModelSchema): ], ) country = field_for(Teams, "country", validate=[validate_country_code]) + fields = Nested( + TeamFieldEntriesSchema, partial=True, many=True, attribute="field_entries" + ) @pre_load def validate_name(self, data): @@ -186,6 +192,126 @@ class TeamSchema(ma.ModelSchema): field_names=["captain_id"], ) + @pre_load + def validate_fields(self, data): + """ + This validator is used to only allow users to update the field entry for their user. + It's not possible to exclude it because without the PK Marshmallow cannot load the right instance + """ + fields = data.get("fields") + if fields is None: + return + + current_team = get_current_team() + + if is_admin(): + team_id = data.get("id") + if team_id: + target_team = Teams.query.filter_by(id=data["id"]).first() + else: + target_team = current_team + + # We are editting an existing + if self.view == "admin" and self.instance: + target_team = self.instance + provided_ids = [] + for f in fields: + f.pop("id", None) + field_id = f.get("field_id") + + # # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce + field = TeamFields.query.filter_by(id=field_id).first_or_404() + + # Get the existing field entry if one exists + entry = TeamFieldEntries.query.filter_by( + field_id=field.id, team_id=target_team.id + ).first() + if entry: + f["id"] = entry.id + provided_ids.append(entry.id) + + # Extremely dirty hack to prevent deleting previously provided data. + # This needs a better soln. + entries = ( + TeamFieldEntries.query.options(load_only("id")) + .filter_by(team_id=target_team.id) + .all() + ) + for entry in entries: + if entry.id not in provided_ids: + fields.append({"id": entry.id}) + else: + provided_ids = [] + for f in fields: + # Remove any existing set + f.pop("id", None) + field_id = f.get("field_id") + value = f.get("value") + + # # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce + field = TeamFields.query.filter_by(id=field_id).first_or_404() + + if field.required is True and value.strip() == "": + raise ValidationError( + f"Field '{field.name}' is required", field_names=["fields"] + ) + + if field.editable is False: + raise ValidationError( + f"Field '{field.name}' cannot be editted", + field_names=["fields"], + ) + + # Get the existing field entry if one exists + entry = TeamFieldEntries.query.filter_by( + field_id=field.id, team_id=current_team.id + ).first() + + if entry: + f["id"] = entry.id + provided_ids.append(entry.id) + + # Extremely dirty hack to prevent deleting previously provided data. + # This needs a better soln. + entries = ( + TeamFieldEntries.query.options(load_only("id")) + .filter_by(team_id=current_team.id) + .all() + ) + for entry in entries: + if entry.id not in provided_ids: + fields.append({"id": entry.id}) + + @post_dump + def process_fields(self, data): + """ + Handle permissions levels for fields. + This is post_dump to manipulate JSON instead of the raw db object + + Admins can see all fields. + Users (self) can see their edittable and public fields + Public (user) can only see public fields + """ + # Gather all possible fields + removed_field_ids = [] + fields = TeamFields.query.all() + + # Select fields for removal based on current view and properties of the field + for field in fields: + if self.view == "user": + if field.public is False: + removed_field_ids.append(field.id) + elif self.view == "self": + if field.editable is False and field.public is False: + removed_field_ids.append(field.id) + + # Rebuild fuilds + fields = data.get("fields") + if fields: + data["fields"] = [ + field for field in fields if field["field_id"] not in removed_field_ids + ] + views = { "user": [ "website", @@ -197,6 +323,7 @@ class TeamSchema(ma.ModelSchema): "id", "oauth_id", "captain_id", + "fields", ], "self": [ "website", @@ -210,6 +337,7 @@ class TeamSchema(ma.ModelSchema): "oauth_id", "password", "captain_id", + "fields", ], "admin": [ "website", @@ -227,6 +355,7 @@ class TeamSchema(ma.ModelSchema): "oauth_id", "password", "captain_id", + "fields", ], } @@ -236,5 +365,6 @@ class TeamSchema(ma.ModelSchema): kwargs["only"] = self.views[view] elif isinstance(view, list): kwargs["only"] = view + self.view = view super(TeamSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/users.py b/CTFd/schemas/users.py index 3b51453b..66393061 100644 --- a/CTFd/schemas/users.py +++ b/CTFd/schemas/users.py @@ -205,45 +205,55 @@ class UserSchema(ma.ModelSchema): else: target_user = current_user - provided_ids = [] - for f in fields: - f.pop("id", None) - field_id = f.get("field_id") + # We are editting an existing user + if self.view == "admin" and self.instance: + target_user = self.instance + provided_ids = [] + for f in fields: + f.pop("id", None) + field_id = f.get("field_id") - # # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce - field = UserFields.query.filter_by(id=field_id).first_or_404() + # # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce + field = UserFields.query.filter_by(id=field_id).first_or_404() - # Get the existing field entry if one exists - entry = UserFieldEntries.query.filter_by( - field_id=field.id, user_id=target_user.id - ).first() - if entry: - f["id"] = entry.id - provided_ids.append(entry.id) + # Get the existing field entry if one exists + entry = UserFieldEntries.query.filter_by( + field_id=field.id, user_id=target_user.id + ).first() + if entry: + f["id"] = entry.id + provided_ids.append(entry.id) - # Extremely dirty hack to prevent deleting previously provided data. - # This needs a better soln. - entries = ( - UserFieldEntries.query.options(load_only("id")) - .filter_by(user_id=target_user.id) - .all() - ) - for entry in entries: - if entry.id not in provided_ids: - fields.append({"id": entry.id}) + # Extremely dirty hack to prevent deleting previously provided data. + # This needs a better soln. + entries = ( + UserFieldEntries.query.options(load_only("id")) + .filter_by(user_id=target_user.id) + .all() + ) + for entry in entries: + if entry.id not in provided_ids: + fields.append({"id": entry.id}) else: provided_ids = [] for f in fields: # Remove any existing set f.pop("id", None) field_id = f.get("field_id") + value = f.get("value") # # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce field = UserFields.query.filter_by(id=field_id).first_or_404() + if field.required is True and value.strip() == "": + raise ValidationError( + f"Field '{field.name}' is required", field_names=["fields"] + ) + if field.editable is False: raise ValidationError( - f"Field {field.name} cannot be editted", field_names=["fields"] + f"Field '{field.name}' cannot be editted", + field_names=["fields"], ) # Get the existing field entry if one exists diff --git a/CTFd/teams.py b/CTFd/teams.py index 142477c8..fecbd874 100644 --- a/CTFd/teams.py +++ b/CTFd/teams.py @@ -1,8 +1,8 @@ from flask import Blueprint, redirect, render_template, request, url_for from CTFd.cache import clear_team_session, clear_user_session -from CTFd.models import Teams, db -from CTFd.utils import config, get_config +from CTFd.models import TeamFieldEntries, TeamFields, Teams, db +from CTFd.utils import config, get_config, validators from CTFd.utils.crypto import verify_password from CTFd.utils.decorators import authed_only, ratelimit from CTFd.utils.decorators.modes import require_team_mode @@ -125,6 +125,9 @@ def new(): passphrase = request.form.get("password", "").strip() errors = get_errors() + website = request.form.get("website") + affiliation = request.form.get("affiliation") + user = get_current_user() existing_team = Teams.query.filter_by(name=teamname).first() @@ -133,14 +136,64 @@ def new(): if not teamname: errors.append("That team name is invalid") + # Process additional user fields + fields = {} + for field in TeamFields.query.all(): + fields[field.id] = field + + entries = {} + for field_id, field in fields.items(): + value = request.form.get(f"fields[{field_id}]", "").strip() + if field.required is True and (value is None or value == ""): + errors.append("Please provide all required fields") + break + + # Handle special casing of existing profile fields + if field.name.lower() == "affiliation": + affiliation = value + break + elif field.name.lower() == "website": + website = value + break + + if field.field_type == "boolean": + entries[field_id] = bool(value) + else: + entries[field_id] = value + + if website: + valid_website = validators.validate_url(website) + else: + valid_website = True + + if affiliation: + valid_affiliation = len(affiliation) < 128 + else: + valid_affiliation = True + + if valid_website is False: + errors.append("Websites must be a proper URL starting with http or https") + if valid_affiliation is False: + errors.append("Please provide a shorter affiliation") + if errors: return render_template("teams/new_team.html", errors=errors) team = Teams(name=teamname, password=passphrase, captain_id=user.id) + if website: + team.website = website + if affiliation: + team.affiliation = affiliation + db.session.add(team) db.session.commit() + for field_id, value in entries.items(): + entry = TeamFieldEntries(field_id=field_id, value=value, team_id=team.id) + db.session.add(entry) + db.session.commit() + user.team_id = team.id db.session.commit() diff --git a/CTFd/themes/admin/assets/js/components/configs/fields/Field.vue b/CTFd/themes/admin/assets/js/components/configs/fields/Field.vue index f02bea77..484a2c20 100644 --- a/CTFd/themes/admin/assets/js/components/configs/fields/Field.vue +++ b/CTFd/themes/admin/assets/js/components/configs/fields/Field.vue @@ -20,7 +20,7 @@ v-model.lazy="field.field_type" > - + Type of field shown to the user { }) .change(); - // Insert CommentBox element + // Insert FieldList element for users const fieldList = Vue.extend(FieldList); - let vueContainer = document.createElement("div"); - document.querySelector("#user-field-list").appendChild(vueContainer); - new fieldList({}).$mount(vueContainer); + let userVueContainer = document.createElement("div"); + document.querySelector("#user-field-list").appendChild(userVueContainer); + new fieldList({ + propsData: { + type: "user" + } + }).$mount(userVueContainer); + + // Insert FieldList element for teams + let teamVueContainer = document.createElement("div"); + document.querySelector("#team-field-list").appendChild(teamVueContainer); + new fieldList({ + propsData: { + type: "team" + } + }).$mount(teamVueContainer); }); diff --git a/CTFd/themes/admin/assets/js/pages/team.js b/CTFd/themes/admin/assets/js/pages/team.js index c759edeb..bf389d1b 100644 --- a/CTFd/themes/admin/assets/js/pages/team.js +++ b/CTFd/themes/admin/assets/js/pages/team.js @@ -11,6 +11,19 @@ function createTeam(event) { event.preventDefault(); const params = $("#team-info-create-form").serializeJSON(true); + params.fields = []; + + for (const property in params) { + if (property.match(/fields\[\d+\]/)) { + let field = {}; + let id = parseInt(property.slice(7, -1)); + field["field_id"] = id; + field["value"] = params[property]; + params.fields.push(field); + delete params[property]; + } + } + CTFd.fetch("/api/v1/teams", { method: "POST", credentials: "same-origin", @@ -28,15 +41,17 @@ function createTeam(event) { const team_id = response.data.id; window.location = CTFd.config.urlRoot + "/admin/teams/" + team_id; } else { - $("#team-info-form > #results").empty(); + $("#team-info-create-form > #results").empty(); Object.keys(response.errors).forEach(function(key, _index) { - $("#team-info-form > #results").append( + $("#team-info-create-form > #results").append( ezBadge({ type: "error", body: response.errors[key] }) ); - const i = $("#team-info-form").find("input[name={0}]".format(key)); + const i = $("#team-info-create-form").find( + "input[name={0}]".format(key) + ); const input = $(i); input.addClass("input-filled-invalid"); input.removeClass("input-filled-valid"); @@ -47,7 +62,20 @@ function createTeam(event) { function updateTeam(event) { event.preventDefault(); - const params = $("#team-info-edit-form").serializeJSON(true); + let params = $("#team-info-edit-form").serializeJSON(true); + + params.fields = []; + + for (const property in params) { + if (property.match(/fields\[\d+\]/)) { + let field = {}; + let id = parseInt(property.slice(7, -1)); + field["field_id"] = id; + field["value"] = params[property]; + params.fields.push(field); + delete params[property]; + } + } CTFd.fetch("/api/v1/teams/" + window.TEAM_ID, { method: "PATCH", diff --git a/CTFd/themes/admin/static/js/components.dev.js b/CTFd/themes/admin/static/js/components.dev.js index 39a8ef9a..f0b0aef9 100644 --- a/CTFd/themes/admin/static/js/components.dev.js +++ b/CTFd/themes/admin/static/js/components.dev.js @@ -188,7 +188,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { ; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _Field = _interopRequireDefault(__webpack_require__(/*! ./Field.vue */ \"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar _default = {\n name: \"FieldList\",\n components: {\n Field: _Field.default\n },\n props: {},\n data: function data() {\n return {\n fields: []\n };\n },\n methods: {\n loadFields: function loadFields() {\n var _this = this;\n\n _CTFd.default.fetch(\"/api/v1/configs/fields?type=user\", {\n method: \"GET\",\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n }\n }).then(function (response) {\n return response.json();\n }).then(function (response) {\n _this.fields = response.data;\n });\n },\n addField: function addField() {\n this.fields.push({\n id: Math.random(),\n type: \"user\",\n field_type: \"text\",\n name: \"\",\n description: \"\",\n editable: false,\n required: false,\n public: false\n });\n },\n removeField: function removeField(index) {\n this.fields.splice(index, 1);\n console.log(this.fields);\n }\n },\n created: function created() {\n this.loadFields();\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/components/configs/fields/FieldList.vue?./node_modules/babel-loader/lib??ref--0!./node_modules/vue-loader/lib??vue-loader-options"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _Field = _interopRequireDefault(__webpack_require__(/*! ./Field.vue */ \"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar _default = {\n name: \"FieldList\",\n components: {\n Field: _Field.default\n },\n props: {\n type: String\n },\n data: function data() {\n return {\n fields: []\n };\n },\n methods: {\n loadFields: function loadFields() {\n var _this = this;\n\n _CTFd.default.fetch(\"/api/v1/configs/fields?type=\".concat(this.type), {\n method: \"GET\",\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n }\n }).then(function (response) {\n return response.json();\n }).then(function (response) {\n _this.fields = response.data;\n });\n },\n addField: function addField() {\n this.fields.push({\n id: Math.random(),\n type: this.type,\n field_type: \"text\",\n name: \"\",\n description: \"\",\n editable: false,\n required: false,\n public: false\n });\n },\n removeField: function removeField(index) {\n this.fields.splice(index, 1);\n console.log(this.fields);\n }\n },\n created: function created() {\n this.loadFields();\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/components/configs/fields/FieldList.vue?./node_modules/babel-loader/lib??ref--0!./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), @@ -235,7 +235,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(module, __webpack_exports__, __webpack_require__) { ; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"border-bottom\" }, [\n _c(\"div\", [\n _c(\n \"button\",\n {\n staticClass: \"close float-right\",\n attrs: { type: \"button\", \"aria-label\": \"Close\" },\n on: {\n click: function($event) {\n return _vm.deleteField()\n }\n }\n },\n [_c(\"span\", { attrs: { \"aria-hidden\": \"true\" } }, [_vm._v(\"×\")])]\n )\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"row\" }, [\n _c(\"div\", { staticClass: \"col-md-3\" }, [\n _c(\"div\", { staticClass: \"form-group\" }, [\n _c(\"label\", [_vm._v(\"Field Type\")]),\n _vm._v(\" \"),\n _c(\n \"select\",\n {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.field_type,\n expression: \"field.field_type\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-control custom-select\",\n on: {\n change: function($event) {\n var $$selectedVal = Array.prototype.filter\n .call($event.target.options, function(o) {\n return o.selected\n })\n .map(function(o) {\n var val = \"_value\" in o ? o._value : o.value\n return val\n })\n _vm.$set(\n _vm.field,\n \"field_type\",\n $event.target.multiple ? $$selectedVal : $$selectedVal[0]\n )\n }\n }\n },\n [\n _c(\"option\", { attrs: { value: \"text\" } }, [\n _vm._v(\"Text Field\")\n ]),\n _vm._v(\" \"),\n _c(\"option\", { attrs: { value: \"checkbox\" } }, [\n _vm._v(\"Checkbox\")\n ])\n ]\n ),\n _vm._v(\" \"),\n _c(\"small\", { staticClass: \"form-text text-muted\" }, [\n _vm._v(\"Type of field shown to the user\")\n ])\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"col-md-9\" }, [\n _c(\"div\", { staticClass: \"form-group\" }, [\n _c(\"label\", [_vm._v(\"Field Name\")]),\n _vm._v(\" \"),\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.name,\n expression: \"field.name\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-control\",\n attrs: { type: \"text\" },\n domProps: { value: _vm.field.name },\n on: {\n change: function($event) {\n return _vm.$set(_vm.field, \"name\", $event.target.value)\n }\n }\n }),\n _vm._v(\" \"),\n _c(\"small\", { staticClass: \"form-text text-muted\" }, [\n _vm._v(\"Field name\")\n ])\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"form-group\" }, [\n _c(\"label\", [_vm._v(\"Field Description\")]),\n _vm._v(\" \"),\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.description,\n expression: \"field.description\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-control\",\n attrs: { type: \"text\" },\n domProps: { value: _vm.field.description },\n on: {\n change: function($event) {\n return _vm.$set(_vm.field, \"description\", $event.target.value)\n }\n }\n }),\n _vm._v(\" \"),\n _c(\n \"small\",\n { staticClass: \"form-text text-muted\", attrs: { id: \"emailHelp\" } },\n [_vm._v(\"Field Description\")]\n )\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"form-check\" }, [\n _c(\"label\", { staticClass: \"form-check-label\" }, [\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.editable,\n expression: \"field.editable\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-check-input\",\n attrs: { type: \"checkbox\" },\n domProps: {\n checked: Array.isArray(_vm.field.editable)\n ? _vm._i(_vm.field.editable, null) > -1\n : _vm.field.editable\n },\n on: {\n change: function($event) {\n var $$a = _vm.field.editable,\n $$el = $event.target,\n $$c = $$el.checked ? true : false\n if (Array.isArray($$a)) {\n var $$v = null,\n $$i = _vm._i($$a, $$v)\n if ($$el.checked) {\n $$i < 0 &&\n _vm.$set(_vm.field, \"editable\", $$a.concat([$$v]))\n } else {\n $$i > -1 &&\n _vm.$set(\n _vm.field,\n \"editable\",\n $$a.slice(0, $$i).concat($$a.slice($$i + 1))\n )\n }\n } else {\n _vm.$set(_vm.field, \"editable\", $$c)\n }\n }\n }\n }),\n _vm._v(\"\\n Editable by user in profile\\n \")\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"form-check\" }, [\n _c(\"label\", { staticClass: \"form-check-label\" }, [\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.required,\n expression: \"field.required\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-check-input\",\n attrs: { type: \"checkbox\" },\n domProps: {\n checked: Array.isArray(_vm.field.required)\n ? _vm._i(_vm.field.required, null) > -1\n : _vm.field.required\n },\n on: {\n change: function($event) {\n var $$a = _vm.field.required,\n $$el = $event.target,\n $$c = $$el.checked ? true : false\n if (Array.isArray($$a)) {\n var $$v = null,\n $$i = _vm._i($$a, $$v)\n if ($$el.checked) {\n $$i < 0 &&\n _vm.$set(_vm.field, \"required\", $$a.concat([$$v]))\n } else {\n $$i > -1 &&\n _vm.$set(\n _vm.field,\n \"required\",\n $$a.slice(0, $$i).concat($$a.slice($$i + 1))\n )\n }\n } else {\n _vm.$set(_vm.field, \"required\", $$c)\n }\n }\n }\n }),\n _vm._v(\"\\n Required on registration\\n \")\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"form-check\" }, [\n _c(\"label\", { staticClass: \"form-check-label\" }, [\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.public,\n expression: \"field.public\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-check-input\",\n attrs: { type: \"checkbox\" },\n domProps: {\n checked: Array.isArray(_vm.field.public)\n ? _vm._i(_vm.field.public, null) > -1\n : _vm.field.public\n },\n on: {\n change: function($event) {\n var $$a = _vm.field.public,\n $$el = $event.target,\n $$c = $$el.checked ? true : false\n if (Array.isArray($$a)) {\n var $$v = null,\n $$i = _vm._i($$a, $$v)\n if ($$el.checked) {\n $$i < 0 &&\n _vm.$set(_vm.field, \"public\", $$a.concat([$$v]))\n } else {\n $$i > -1 &&\n _vm.$set(\n _vm.field,\n \"public\",\n $$a.slice(0, $$i).concat($$a.slice($$i + 1))\n )\n }\n } else {\n _vm.$set(_vm.field, \"public\", $$c)\n }\n }\n }\n }),\n _vm._v(\"\\n Shown on public profile\\n \")\n ])\n ])\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"row pb-3\" }, [\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"d-block\" }, [\n _c(\n \"button\",\n {\n staticClass: \"btn btn-sm btn-success btn-outlined float-right\",\n attrs: { type: \"button\" },\n on: {\n click: function($event) {\n return _vm.saveField()\n }\n }\n },\n [_vm._v(\"\\n Save\\n \")]\n )\n ])\n ])\n ])\n ])\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"border-bottom\" }, [\n _c(\"div\", [\n _c(\n \"button\",\n {\n staticClass: \"close float-right\",\n attrs: { type: \"button\", \"aria-label\": \"Close\" },\n on: {\n click: function($event) {\n return _vm.deleteField()\n }\n }\n },\n [_c(\"span\", { attrs: { \"aria-hidden\": \"true\" } }, [_vm._v(\"×\")])]\n )\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"row\" }, [\n _c(\"div\", { staticClass: \"col-md-3\" }, [\n _c(\"div\", { staticClass: \"form-group\" }, [\n _c(\"label\", [_vm._v(\"Field Type\")]),\n _vm._v(\" \"),\n _c(\n \"select\",\n {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.field_type,\n expression: \"field.field_type\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-control custom-select\",\n on: {\n change: function($event) {\n var $$selectedVal = Array.prototype.filter\n .call($event.target.options, function(o) {\n return o.selected\n })\n .map(function(o) {\n var val = \"_value\" in o ? o._value : o.value\n return val\n })\n _vm.$set(\n _vm.field,\n \"field_type\",\n $event.target.multiple ? $$selectedVal : $$selectedVal[0]\n )\n }\n }\n },\n [\n _c(\"option\", { attrs: { value: \"text\" } }, [\n _vm._v(\"Text Field\")\n ]),\n _vm._v(\" \"),\n _c(\"option\", { attrs: { value: \"boolean\" } }, [\n _vm._v(\"Checkbox\")\n ])\n ]\n ),\n _vm._v(\" \"),\n _c(\"small\", { staticClass: \"form-text text-muted\" }, [\n _vm._v(\"Type of field shown to the user\")\n ])\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"col-md-9\" }, [\n _c(\"div\", { staticClass: \"form-group\" }, [\n _c(\"label\", [_vm._v(\"Field Name\")]),\n _vm._v(\" \"),\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.name,\n expression: \"field.name\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-control\",\n attrs: { type: \"text\" },\n domProps: { value: _vm.field.name },\n on: {\n change: function($event) {\n return _vm.$set(_vm.field, \"name\", $event.target.value)\n }\n }\n }),\n _vm._v(\" \"),\n _c(\"small\", { staticClass: \"form-text text-muted\" }, [\n _vm._v(\"Field name\")\n ])\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"form-group\" }, [\n _c(\"label\", [_vm._v(\"Field Description\")]),\n _vm._v(\" \"),\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.description,\n expression: \"field.description\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-control\",\n attrs: { type: \"text\" },\n domProps: { value: _vm.field.description },\n on: {\n change: function($event) {\n return _vm.$set(_vm.field, \"description\", $event.target.value)\n }\n }\n }),\n _vm._v(\" \"),\n _c(\n \"small\",\n { staticClass: \"form-text text-muted\", attrs: { id: \"emailHelp\" } },\n [_vm._v(\"Field Description\")]\n )\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"form-check\" }, [\n _c(\"label\", { staticClass: \"form-check-label\" }, [\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.editable,\n expression: \"field.editable\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-check-input\",\n attrs: { type: \"checkbox\" },\n domProps: {\n checked: Array.isArray(_vm.field.editable)\n ? _vm._i(_vm.field.editable, null) > -1\n : _vm.field.editable\n },\n on: {\n change: function($event) {\n var $$a = _vm.field.editable,\n $$el = $event.target,\n $$c = $$el.checked ? true : false\n if (Array.isArray($$a)) {\n var $$v = null,\n $$i = _vm._i($$a, $$v)\n if ($$el.checked) {\n $$i < 0 &&\n _vm.$set(_vm.field, \"editable\", $$a.concat([$$v]))\n } else {\n $$i > -1 &&\n _vm.$set(\n _vm.field,\n \"editable\",\n $$a.slice(0, $$i).concat($$a.slice($$i + 1))\n )\n }\n } else {\n _vm.$set(_vm.field, \"editable\", $$c)\n }\n }\n }\n }),\n _vm._v(\"\\n Editable by user in profile\\n \")\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"form-check\" }, [\n _c(\"label\", { staticClass: \"form-check-label\" }, [\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.required,\n expression: \"field.required\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-check-input\",\n attrs: { type: \"checkbox\" },\n domProps: {\n checked: Array.isArray(_vm.field.required)\n ? _vm._i(_vm.field.required, null) > -1\n : _vm.field.required\n },\n on: {\n change: function($event) {\n var $$a = _vm.field.required,\n $$el = $event.target,\n $$c = $$el.checked ? true : false\n if (Array.isArray($$a)) {\n var $$v = null,\n $$i = _vm._i($$a, $$v)\n if ($$el.checked) {\n $$i < 0 &&\n _vm.$set(_vm.field, \"required\", $$a.concat([$$v]))\n } else {\n $$i > -1 &&\n _vm.$set(\n _vm.field,\n \"required\",\n $$a.slice(0, $$i).concat($$a.slice($$i + 1))\n )\n }\n } else {\n _vm.$set(_vm.field, \"required\", $$c)\n }\n }\n }\n }),\n _vm._v(\"\\n Required on registration\\n \")\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"form-check\" }, [\n _c(\"label\", { staticClass: \"form-check-label\" }, [\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.field.public,\n expression: \"field.public\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-check-input\",\n attrs: { type: \"checkbox\" },\n domProps: {\n checked: Array.isArray(_vm.field.public)\n ? _vm._i(_vm.field.public, null) > -1\n : _vm.field.public\n },\n on: {\n change: function($event) {\n var $$a = _vm.field.public,\n $$el = $event.target,\n $$c = $$el.checked ? true : false\n if (Array.isArray($$a)) {\n var $$v = null,\n $$i = _vm._i($$a, $$v)\n if ($$el.checked) {\n $$i < 0 &&\n _vm.$set(_vm.field, \"public\", $$a.concat([$$v]))\n } else {\n $$i > -1 &&\n _vm.$set(\n _vm.field,\n \"public\",\n $$a.slice(0, $$i).concat($$a.slice($$i + 1))\n )\n }\n } else {\n _vm.$set(_vm.field, \"public\", $$c)\n }\n }\n }\n }),\n _vm._v(\"\\n Shown on public profile\\n \")\n ])\n ])\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"row pb-3\" }, [\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"d-block\" }, [\n _c(\n \"button\",\n {\n staticClass: \"btn btn-sm btn-success btn-outlined float-right\",\n attrs: { type: \"button\" },\n on: {\n click: function($event) {\n return _vm.saveField()\n }\n }\n },\n [_vm._v(\"\\n Save\\n \")]\n )\n ])\n ])\n ])\n ])\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), diff --git a/CTFd/themes/admin/static/js/components.min.js b/CTFd/themes/admin/static/js/components.min.js index f8fca7d3..7bd451f1 100644 --- a/CTFd/themes/admin/static/js/components.min.js +++ b/CTFd/themes/admin/static/js/components.min.js @@ -1 +1 @@ -(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue":function(e,t,s){s.r(t);var i=s("./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=template&id=1fd2c08a&scoped=true&"),a=s("./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=script&lang=js&");for(var n in a)"default"!==n&&function(e){s.d(t,e,function(){return a[e]})}(n);s("./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=style&index=0&id=1fd2c08a&scoped=true&lang=css&");var l=s("./node_modules/vue-loader/lib/runtime/componentNormalizer.js"),o=Object(l.a)(a.default,i.a,i.b,!1,null,"1fd2c08a",null);o.options.__file="CTFd/themes/admin/assets/js/components/comments/CommentBox.vue",t.default=o.exports},"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=script&lang=js&":function(e,t,s){s.r(t);var i=s("./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=script&lang=js&"),a=s.n(i);for(var n in i)"default"!==n&&function(e){s.d(t,e,function(){return i[e]})}(n);t.default=a.a},"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=style&index=0&id=1fd2c08a&scoped=true&lang=css&":function(e,t,s){var i=s("./node_modules/vue-style-loader/index.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=style&index=0&id=1fd2c08a&scoped=true&lang=css&");s.n(i).a},"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=template&id=1fd2c08a&scoped=true&":function(e,t,s){function i(){var s=this,e=s.$createElement,i=s._self._c||e;return i("div",[i("div",{staticClass:"row mb-3"},[i("div",{staticClass:"col-md-12"},[i("div",{staticClass:"comment"},[i("textarea",{directives:[{name:"model",rawName:"v-model.lazy",value:s.comment,expression:"comment",modifiers:{lazy:!0}}],staticClass:"form-control mb-2",attrs:{rows:"2",id:"comment-input",placeholder:"Add comment"},domProps:{value:s.comment},on:{change:function(e){s.comment=e.target.value}}}),s._v(" "),i("button",{staticClass:"btn btn-sm btn-success btn-outlined float-right",attrs:{type:"submit"},on:{click:function(e){return s.submitComment()}}},[s._v("\n Comment\n ")])])])]),s._v(" "),1>>\n ")])])]),s._v(" "),i("div",{staticClass:"col-md-12"},[i("div",{staticClass:"text-center"},[i("small",{staticClass:"text-muted"},[s._v("Page "+s._s(s.page)+" of "+s._s(s.total)+" comments")])])])]):s._e(),s._v(" "),i("div",{staticClass:"comments"},[i("transition-group",{attrs:{name:"comment-card"}},s._l(s.comments,function(t){return i("div",{key:t.id,staticClass:"comment-card card mb-2"},[i("div",{staticClass:"card-body pl-0 pb-0 pt-2 pr-2"},[i("button",{staticClass:"close float-right",attrs:{type:"button","aria-label":"Close"},on:{click:function(e){return s.deleteComment(t.id)}}},[i("span",{attrs:{"aria-hidden":"true"}},[s._v("×")])])]),s._v(" "),i("div",{staticClass:"card-body"},[i("div",{staticClass:"card-text",domProps:{innerHTML:s._s(t.html)}}),s._v(" "),i("small",{staticClass:"text-muted float-left"},[i("span",[i("a",{attrs:{href:s.urlRoot+"/admin/users/"+t.author_id}},[s._v(s._s(t.author.name))])])]),s._v(" "),i("small",{staticClass:"text-muted float-right"},[i("span",{staticClass:"float-right"},[s._v(s._s(s.toLocalTime(t.date)))])])])])}),0)],1),s._v(" "),1>>\n ")])])]),s._v(" "),i("div",{staticClass:"col-md-12"},[i("div",{staticClass:"text-center"},[i("small",{staticClass:"text-muted"},[s._v("Page "+s._s(s.page)+" of "+s._s(s.total)+" comments")])])])]):s._e()])}var a=[];i._withStripped=!0,s.d(t,"a",function(){return i}),s.d(t,"b",function(){return a})},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue":function(e,t,s){s.r(t);var i=s("./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=template&id=30e0f744&scoped=true&"),a=s("./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&");for(var n in a)"default"!==n&&function(e){s.d(t,e,function(){return a[e]})}(n);var l=s("./node_modules/vue-loader/lib/runtime/componentNormalizer.js"),o=Object(l.a)(a.default,i.a,i.b,!1,null,"30e0f744",null);o.options.__file="CTFd/themes/admin/assets/js/components/configs/fields/Field.vue",t.default=o.exports},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&":function(e,t,s){s.r(t);var i=s("./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&"),a=s.n(i);for(var n in i)"default"!==n&&function(e){s.d(t,e,function(){return i[e]})}(n);t.default=a.a},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=template&id=30e0f744&scoped=true&":function(e,t,s){function i(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{staticClass:"border-bottom"},[t("div",[t("button",{staticClass:"close float-right",attrs:{type:"button","aria-label":"Close"},on:{click:function(e){return n.deleteField()}}},[t("span",{attrs:{"aria-hidden":"true"}},[n._v("×")])])]),n._v(" "),t("div",{staticClass:"row"},[t("div",{staticClass:"col-md-3"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Type")]),n._v(" "),t("select",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.field_type,expression:"field.field_type",modifiers:{lazy:!0}}],staticClass:"form-control custom-select",on:{change:function(e){var t=Array.prototype.filter.call(e.target.options,function(e){return e.selected}).map(function(e){return"_value"in e?e._value:e.value});n.$set(n.field,"field_type",e.target.multiple?t:t[0])}}},[t("option",{attrs:{value:"text"}},[n._v("Text Field")]),n._v(" "),t("option",{attrs:{value:"checkbox"}},[n._v("Checkbox")])]),n._v(" "),t("small",{staticClass:"form-text text-muted"},[n._v("Type of field shown to the user")])])]),n._v(" "),t("div",{staticClass:"col-md-9"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Name")]),n._v(" "),t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.name,expression:"field.name",modifiers:{lazy:!0}}],staticClass:"form-control",attrs:{type:"text"},domProps:{value:n.field.name},on:{change:function(e){return n.$set(n.field,"name",e.target.value)}}}),n._v(" "),t("small",{staticClass:"form-text text-muted"},[n._v("Field name")])])]),n._v(" "),t("div",{staticClass:"col-md-12"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Description")]),n._v(" "),t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.description,expression:"field.description",modifiers:{lazy:!0}}],staticClass:"form-control",attrs:{type:"text"},domProps:{value:n.field.description},on:{change:function(e){return n.$set(n.field,"description",e.target.value)}}}),n._v(" "),t("small",{staticClass:"form-text text-muted",attrs:{id:"emailHelp"}},[n._v("Field Description")])])]),n._v(" "),t("div",{staticClass:"col-md-12"},[t("div",{staticClass:"form-check"},[t("label",{staticClass:"form-check-label"},[t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.editable,expression:"field.editable",modifiers:{lazy:!0}}],staticClass:"form-check-input",attrs:{type:"checkbox"},domProps:{checked:Array.isArray(n.field.editable)?-1>>\n ")])])]),s._v(" "),i("div",{staticClass:"col-md-12"},[i("div",{staticClass:"text-center"},[i("small",{staticClass:"text-muted"},[s._v("Page "+s._s(s.page)+" of "+s._s(s.total)+" comments")])])])]):s._e(),s._v(" "),i("div",{staticClass:"comments"},[i("transition-group",{attrs:{name:"comment-card"}},s._l(s.comments,function(t){return i("div",{key:t.id,staticClass:"comment-card card mb-2"},[i("div",{staticClass:"card-body pl-0 pb-0 pt-2 pr-2"},[i("button",{staticClass:"close float-right",attrs:{type:"button","aria-label":"Close"},on:{click:function(e){return s.deleteComment(t.id)}}},[i("span",{attrs:{"aria-hidden":"true"}},[s._v("×")])])]),s._v(" "),i("div",{staticClass:"card-body"},[i("div",{staticClass:"card-text",domProps:{innerHTML:s._s(t.html)}}),s._v(" "),i("small",{staticClass:"text-muted float-left"},[i("span",[i("a",{attrs:{href:s.urlRoot+"/admin/users/"+t.author_id}},[s._v(s._s(t.author.name))])])]),s._v(" "),i("small",{staticClass:"text-muted float-right"},[i("span",{staticClass:"float-right"},[s._v(s._s(s.toLocalTime(t.date)))])])])])}),0)],1),s._v(" "),1>>\n ")])])]),s._v(" "),i("div",{staticClass:"col-md-12"},[i("div",{staticClass:"text-center"},[i("small",{staticClass:"text-muted"},[s._v("Page "+s._s(s.page)+" of "+s._s(s.total)+" comments")])])])]):s._e()])}var a=[];i._withStripped=!0,s.d(t,"a",function(){return i}),s.d(t,"b",function(){return a})},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue":function(e,t,s){s.r(t);var i=s("./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=template&id=30e0f744&scoped=true&"),a=s("./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&");for(var n in a)"default"!==n&&function(e){s.d(t,e,function(){return a[e]})}(n);var l=s("./node_modules/vue-loader/lib/runtime/componentNormalizer.js"),o=Object(l.a)(a.default,i.a,i.b,!1,null,"30e0f744",null);o.options.__file="CTFd/themes/admin/assets/js/components/configs/fields/Field.vue",t.default=o.exports},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&":function(e,t,s){s.r(t);var i=s("./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&"),a=s.n(i);for(var n in i)"default"!==n&&function(e){s.d(t,e,function(){return i[e]})}(n);t.default=a.a},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=template&id=30e0f744&scoped=true&":function(e,t,s){function i(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{staticClass:"border-bottom"},[t("div",[t("button",{staticClass:"close float-right",attrs:{type:"button","aria-label":"Close"},on:{click:function(e){return n.deleteField()}}},[t("span",{attrs:{"aria-hidden":"true"}},[n._v("×")])])]),n._v(" "),t("div",{staticClass:"row"},[t("div",{staticClass:"col-md-3"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Type")]),n._v(" "),t("select",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.field_type,expression:"field.field_type",modifiers:{lazy:!0}}],staticClass:"form-control custom-select",on:{change:function(e){var t=Array.prototype.filter.call(e.target.options,function(e){return e.selected}).map(function(e){return"_value"in e?e._value:e.value});n.$set(n.field,"field_type",e.target.multiple?t:t[0])}}},[t("option",{attrs:{value:"text"}},[n._v("Text Field")]),n._v(" "),t("option",{attrs:{value:"boolean"}},[n._v("Checkbox")])]),n._v(" "),t("small",{staticClass:"form-text text-muted"},[n._v("Type of field shown to the user")])])]),n._v(" "),t("div",{staticClass:"col-md-9"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Name")]),n._v(" "),t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.name,expression:"field.name",modifiers:{lazy:!0}}],staticClass:"form-control",attrs:{type:"text"},domProps:{value:n.field.name},on:{change:function(e){return n.$set(n.field,"name",e.target.value)}}}),n._v(" "),t("small",{staticClass:"form-text text-muted"},[n._v("Field name")])])]),n._v(" "),t("div",{staticClass:"col-md-12"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Description")]),n._v(" "),t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.description,expression:"field.description",modifiers:{lazy:!0}}],staticClass:"form-control",attrs:{type:"text"},domProps:{value:n.field.description},on:{change:function(e){return n.$set(n.field,"description",e.target.value)}}}),n._v(" "),t("small",{staticClass:"form-text text-muted",attrs:{id:"emailHelp"}},[n._v("Field Description")])])]),n._v(" "),t("div",{staticClass:"col-md-12"},[t("div",{staticClass:"form-check"},[t("label",{staticClass:"form-check-label"},[t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.editable,expression:"field.editable",modifiers:{lazy:!0}}],staticClass:"form-check-input",attrs:{type:"checkbox"},domProps:{checked:Array.isArray(n.field.editable)?-1\").text(_momentTimezone.default.tz.guess());\n (0, _jquery.default)(target).append(current);\n\n var tz_names = _momentTimezone.default.tz.names();\n\n for (var i = 0; i < tz_names.length; i++) {\n var tz = (0, _jquery.default)(\"