diff --git a/app/Console/Commands/CheckPasswordExpiry.php b/app/Console/Commands/CheckPasswordExpiry.php new file mode 100644 index 0000000..01634bc --- /dev/null +++ b/app/Console/Commands/CheckPasswordExpiry.php @@ -0,0 +1,81 @@ +argument('ope_id'); + + if ($opeId) { + $ope = Ope::find($opeId); + if (!$ope) { + $this->error("Ope with ID {$opeId} not found"); + return 1; + } + $this->checkOpe($ope); + } else { + // すべてのオペレータをチェック + $opes = Ope::all(); + foreach ($opes as $ope) { + $this->checkOpe($ope); + } + } + + return 0; + } + + /** + * 単一オペレータの有効期限をチェック + */ + private function checkOpe(Ope $ope): void + { + $this->info("=== Ope ID: {$ope->ope_id} ({$ope->ope_name}) ==="); + $this->info("ope_pass_changed_at: " . ($ope->ope_pass_changed_at ?? 'NULL')); + + if (is_null($ope->ope_pass_changed_at)) { + $this->warn("❌ Password change REQUIRED (never changed)"); + return; + } + + try { + $changedAt = Carbon::parse($ope->ope_pass_changed_at); + $now = Carbon::now(); + $monthsDiff = $now->diffInMonths($changedAt); + + $this->info("Changed At: " . $changedAt->format('Y-m-d H:i:s')); + $this->info("Now: " . $now->format('Y-m-d H:i:s')); + $this->info("Months Difference: {$monthsDiff}"); + + if ($monthsDiff >= 3) { + $this->warn("❌ Password change REQUIRED ({$monthsDiff} months passed)"); + } else { + $this->line("✅ Password is valid ({$monthsDiff} months passed, {3 - $monthsDiff} months remaining)"); + } + } catch (\Exception $e) { + $this->error("Error parsing date: {$e->getMessage()}"); + } + + $this->line(""); + } +} diff --git a/app/Http/Controllers/Admin/CityController.php b/app/Http/Controllers/Admin/CityController.php index 82c82a7..55cba28 100644 --- a/app/Http/Controllers/Admin/CityController.php +++ b/app/Http/Controllers/Admin/CityController.php @@ -20,23 +20,20 @@ final class CityController extends Controller $sortType = (string) $request->input('sort_type', 'asc'); $page = (int) $request->get('page', 1); - // ソート許可(安全 + 規約) - $sortable = ['city_id', 'city_name', 'print_layout', 'city_remarks', 'created_at', 'updated_at']; - if (!in_array($sort, $sortable, true)) { - $sort = 'city_id'; + $query = City::query(); + + if ($request->filled('city_name')) { + $query->where('city_name', 'like', '%' . $request->input('city_name') . '%'); } - $sortType = strtolower($sortType); - if (!in_array($sortType, ['asc', 'desc'], true)) { - $sortType = 'asc'; + // 排序处理 + if (!empty($sort)) { + $query->orderBy($sort, $sortType); } - $list = $service->paginateList( - $request->input('city_name'), - $sort, - $sortType - ); + $list = $query->paginate(20); + // 页码越界处理 if ($list->total() > 0 && $page > $list->lastPage()) { return redirect()->route('cities.index', [ 'sort' => $sort, @@ -54,56 +51,127 @@ final class CityController extends Controller public function create(): View { - return view('admin.cities.create', [ - 'record' => new City(), - ]); - } + $inputs = [ + 'city_name' => '', + 'print_layout' => '', + 'city_user' => '', + 'city_remarks' => '', + ]; - public function store(CityRequest $request, CityService $service): RedirectResponse - { - $service->create($request->validated()); + if ($request->isMethod('POST')) { + $rules = [ + 'city_name' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'], + 'print_layout' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'], + 'city_user' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'], + 'city_remarks' => ['nullable', 'string', 'max:20'], + ]; + $messages = [ + 'city_name.required' => '市区名は必須です。', + 'city_name.regex' => '市区名は全角で入力してください。', + 'print_layout.required' => '印字レイアウトファイルは必須です。', + 'print_layout.regex' => '印字レイアウトファイルは全角で入力してください。', + 'city_user.required' => '顧客M入力不要フィールドIDは必須です。', + 'city_user.regex' => '顧客M入力不要フィールドIDは全角で入力してください。', + 'city_remarks.max' => '備考は20文字以内で入力してください。', + ]; + $validator = Validator::make($request->all(), $rules, $messages); - return redirect() - ->route('cities.index') - ->with('success', __('登録に成功しました')); - } + $inputs = array_merge($inputs, $request->all()); - public function edit(int $id): View - { - $city = City::findOrFail($id); + if (!$validator->fails()) { + $maxId = DB::table('city')->max('city_id'); + $newCityId = $maxId ? $maxId + 1 : 1; - return view('admin.cities.edit', [ - 'record' => $city, - ]); - } + $city = new City(); + $city->city_id = $newCityId; + $city->fill($request->only([ + 'city_name', + 'print_layout', + 'city_user', + 'city_remarks', + ])); - public function update(CityRequest $request, int $id, CityService $service): RedirectResponse - { - $city = City::findOrFail($id); - - $service->update($city, $request->validated()); - - return redirect() - ->route('cities.index') - ->with('success', __('更新に成功しました')); - } - - public function destroy(Request $request): RedirectResponse - { - $ids = $request->input('pk'); - - // pk が単体でも配列でも受けられるようにする(編集画面/一覧画面両対応) - if ($ids === null || $ids === '' || $ids === []) { - return redirect() - ->route('cities.index') - ->with('error', __('削除する市区を選択してください。')); + if ($city->save()) { + $request->session()->flash('success', __('登録に成功しました')); + return redirect()->route('city'); + } else { + $request->session()->flash('error', __('登録に失敗しました')); + } + } else { + $inputs['errorMsg'] = $validator->errors()->all(); + } } - $ids = is_array($ids) ? $ids : [$ids]; - $deleted = City::destroy($ids); - - return $deleted - ? redirect()->route('cities.index')->with('success', __('削除が完了しました。')) - : redirect()->route('cities.index')->with('error', __('削除に失敗しました。')); + return view('admin.CityMaster.add', $inputs); } -} + + public function edit(Request $request, $pk, $view = '') + { + $city = City::find($pk); + if (!$city) { + abort(404); + } + + if ($request->isMethod('POST')) { + $rules = [ + 'city_name' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'], + 'print_layout' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'], + 'city_user' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'], + 'city_remarks' => ['nullable', 'string', 'max:20'], + ]; + $messages = [ + 'city_name.required' => '市区名は必須です。', + 'city_name.regex' => '市区名は全角で入力してください。', + 'print_layout.required' => '印字レイアウトファイルは必須です。', + 'print_layout.regex' => '印字レイアウトファイルは全角で入力してください。', + 'city_user.required' => '顧客M入力不要フィールドIDは必須です。', + 'city_user.regex' => '顧客M入力不要フィールドIDは全角で入力してください。', + 'city_remarks.max' => '備考は20文字以内で入力してください。', + ]; + $validator = Validator::make($request->all(), $rules, $messages); + + if (!$validator->fails()) { + $city->fill($request->only([ + 'city_name', + 'print_layout', + 'city_user', + 'city_remarks', + ])); + + if ($city->save()) { + $request->session()->flash('success', __('更新に成功しました')); + return redirect()->route('city'); + } else { + $request->session()->flash('error', __('更新に失敗しました')); + } + } else { + return view('admin.CityMaster.edit', [ + 'city' => $city, + 'errorMsg' => $validator->errors()->all(), + ]); + } + } + + return view($view ?: 'admin.CityMaster.edit', [ + 'city' => $city, + ]); + } + + public function info(Request $request, $pk) + { + return $this->edit($request, $pk, 'CityMaster.info'); + } + + public function delete(Request $request) + { + $arr_pk = $request->get('pk'); + if (!$arr_pk) { + return redirect()->route('city')->with('error', __('削除する市区を選択してください。')); + } + if (City::destroy($arr_pk)) { + return redirect()->route('city')->with('success', __("削除が完了しました。")); + } else { + return redirect()->route('city')->with('error', __('削除に失敗しました。')); + } + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/InformationController.php b/app/Http/Controllers/Admin/InformationController.php index 0b87019..a44c017 100644 --- a/app/Http/Controllers/Admin/InformationController.php +++ b/app/Http/Controllers/Admin/InformationController.php @@ -56,6 +56,189 @@ class InformationController extends Controller return view('admin.information.list', compact('jobs','period','type','status')); } + // ダッシュボード表示 + public function dashboard(Request $request) + { + // ダッシュボード統計情報を集計 + + // park_number テーブルから総容量を計算 + // park_standard(標準) + park_number(割当)+ park_limit(制限値)の合算 + $totalCapacity = DB::table('park_number') + ->selectRaw(' + COALESCE(SUM(park_standard), 0) as std_sum, + COALESCE(SUM(park_number), 0) as num_sum, + COALESCE(SUM(park_limit), 0) as limit_sum + ') + ->first(); + + $totalCapacityValue = ($totalCapacity->std_sum ?? 0) + + ($totalCapacity->num_sum ?? 0) + + ($totalCapacity->limit_sum ?? 0); + + // 予約待ち人数(reserve テーブルから集計) + // 条件:有効(valid_flag=1) かつ契約化されていない(contract_id IS NULL) + // キャンセル除外:reserve_cancel_flag が NULL または 0、かつ reserve_cancelday が NULL + $reserveQuery = DB::table('reserve') + ->where('valid_flag', 1) + ->whereNull('contract_id'); + + // キャンセルフラグの有無をチェック(列が存在するかどうか) + try { + $testResult = DB::table('reserve') + ->select(DB::raw('1')) + ->whereNotNull('reserve_cancel_flag') + ->limit(1) + ->first(); + + // 列が存在する場合、キャンセル除外条件を追加 + $reserveQuery = $reserveQuery + ->where(function ($q) { + $q->whereNull('reserve_cancel_flag') + ->orWhere('reserve_cancel_flag', 0); + }) + ->whereNull('reserve_cancelday'); + } catch (\Exception $e) { + // キャンセルフラグが未運用の場合は基本条件のみで計算 + } + + $totalWaiting = $reserveQuery->count(); + + // 使用中台数(park_number の park_number が使用台数) + $totalUsed = DB::table('park_number') + ->sum('park_number') ?? 0; + + // 空き台数 = 総容量 - 使用中台数 + $totalVacant = max(0, $totalCapacityValue - $totalUsed); + + // 利用率計算(小数点以下切捨て) + $utilizationRate = $totalCapacityValue > 0 + ? (int) floor(($totalUsed / $totalCapacityValue) * 100) + : 0; + + // 予約待ち率(超過時のみ、超過なしは0%) + // 超過判定:待機人数 > 空き台数 + $totalWaitingRate = 0; + if ($totalCapacityValue > 0 && $totalWaiting > 0 && $totalWaiting > $totalVacant) { + // 超過分 / 総容量 * 100(分母チェック付き) + $totalWaitingRate = (int) floor((($totalWaiting - $totalVacant) / $totalCapacityValue) * 100); + } + + $totalStats = [ + 'total_cities' => DB::table('city')->count(), + 'total_parks' => DB::table('park')->count(), + 'total_contracts' => DB::table('regular_contract')->count(), + 'total_users' => DB::table('user')->count(), + 'total_devices' => DB::table('device')->count(), + 'today_queues' => DB::table('operator_que') + ->whereDate('created_at', today()) + ->count(), + 'total_waiting' => $totalWaiting, + 'total_capacity' => $totalCapacityValue, + 'total_utilization_rate' => $utilizationRate, + 'total_vacant_number' => $totalVacant, + 'total_waiting_rate' => $totalWaitingRate, + ]; + + // 自治体別統計情報を作成 + $cityStats = []; + $cities = DB::table('city')->get(); + + foreach ($cities as $city) { + // その自治体に属する駐輪場 ID を取得 + $parkIds = DB::table('park') + ->where('city_id', $city->city_id) + ->pluck('park_id') + ->toArray(); + + // ① 駐輪場数 + $parksCount = count($parkIds); + + // ② 総収容台数(park_number テーブルの park_standard を合算) + $capacity = 0; + if (!empty($parkIds)) { + $capacityResult = DB::table('park_number') + ->whereIn('park_id', $parkIds) + ->sum('park_standard'); + $capacity = $capacityResult ?? 0; + } + + // ③ 契約台数(contract_cancel_flag = 0 かつ有効期間内) + $contractsCount = 0; + if (!empty($parkIds)) { + $contractsCount = DB::table('regular_contract') + ->whereIn('park_id', $parkIds) + ->where('contract_cancel_flag', 0) + ->where(function ($q) { + // 有効期間内:開始日 <= 今日 かつ 終了日 >= 今日 + $q->where('contract_periods', '<=', now()) + ->where('contract_periode', '>=', now()); + }) + ->count(); + } + + // ④ 利用率計算(小数点以下切捨て) + $utilizationRate = $capacity > 0 + ? (int) floor(($contractsCount / $capacity) * 100) + : 0; + + // ⑤ 空き台数 + $availableSpaces = max(0, $capacity - $contractsCount); + + // ⑥ 予約待ち人数(reserve テーブルで contract_id IS NULL かつ valid_flag = 1) + $waitingCount = 0; + if (!empty($parkIds)) { + $waitingQuery = DB::table('reserve') + ->whereIn('park_id', $parkIds) + ->where('valid_flag', 1) + ->whereNull('contract_id'); + + // キャンセルフラグの有無をチェック + try { + DB::table('reserve') + ->select(DB::raw('1')) + ->whereNotNull('reserve_cancel_flag') + ->limit(1) + ->first(); + + // 列が存在する場合、キャンセル除外条件を追加 + $waitingQuery = $waitingQuery + ->where(function ($q) { + $q->whereNull('reserve_cancel_flag') + ->orWhere('reserve_cancel_flag', 0); + }) + ->whereNull('reserve_cancelday'); + } catch (\Exception $e) { + // キャンセルフラグが未運用の場合は基本条件のみで計算 + } + + $waitingCount = $waitingQuery->count(); + } + + // ⑦ 利用者数(ユニークユーザー数) + $usersCount = 0; + if (!empty($parkIds)) { + $usersCount = DB::table('regular_contract') + ->whereIn('park_id', $parkIds) + ->distinct() + ->count('user_id'); + } + + // 配列に追加 + $cityStats[] = [ + 'city' => $city, + 'parks_count' => $parksCount, + 'contracts_count' => $contractsCount, + 'users_count' => $usersCount, + 'waiting_count' => $waitingCount, + 'capacity' => $capacity, + 'utilization_rate' => $utilizationRate, + 'available_spaces' => $availableSpaces, + ]; + } + + return view('admin.information.dashboard', compact('totalStats', 'cityStats')); + } + // ステータス一括更新(着手=2 / 対応完了=3) public function updateStatus(Request $request) { diff --git a/app/Http/Controllers/Admin/OpeController.php b/app/Http/Controllers/Admin/OpeController.php index d1e6c88..183b888 100644 --- a/app/Http/Controllers/Admin/OpeController.php +++ b/app/Http/Controllers/Admin/OpeController.php @@ -1,4 +1,5 @@ isMethod('get')) { - - return view('admin.opes.add', [ + // ※機能(画面)一覧を取得(プルダウン用) + $features = Feature::query() + ->orderBy('id') + ->get(['id', 'name']); + // ※操作権限一覧を取得(チェックボックス用) + $permissions = Permission::query() + ->orderBy('id') + ->get(['id', 'code', 'name']); + + if ($request->isMethod('get')) { + return view('admin.opes.add', [ 'isEdit' => false, - 'record' => new Ope(), - 'ope_id' => null, - 'ope_name' => '', - 'ope_type' => '', - 'ope_mail' => '', - 'ope_phone'=> '', + 'record' => new Ope(), + + 'ope_id' => null, + 'ope_name' => '', + 'ope_type' => '', + 'ope_mail' => '', + 'ope_phone' => '', 'ope_sendalart_que1' => 0, 'ope_sendalart_que2' => 0, 'ope_sendalart_que3' => 0, 'ope_sendalart_que4' => 0, 'ope_sendalart_que5' => 0, 'ope_sendalart_que6' => 0, 'ope_sendalart_que7' => 0, 'ope_sendalart_que8' => 0, 'ope_sendalart_que9' => 0, - 'ope_sendalart_que10'=> 0, 'ope_sendalart_que11'=> 0, 'ope_sendalart_que12'=> 0, - 'ope_sendalart_que13'=> 0, + 'ope_sendalart_que10' => 0, 'ope_sendalart_que11' => 0, 'ope_sendalart_que12' => 0, + 'ope_sendalart_que13' => 0, 'ope_auth1' => '', 'ope_auth2' => '', 'ope_auth3' => '', 'ope_auth4' => '', 'ope_quit_flag' => 0, 'ope_quitday' => '', + + // ▼追加:権限設定UI用 + 'features' => $features, + 'permissions' => $permissions, + 'selectedFeatureId' => old('feature_id', null), ]); } @@ -96,36 +113,73 @@ class OpeController extends Controller return redirect()->route('opes')->with('success', '登録しました。'); } - - /** * 編集(GET 画面 / POST 更新) + * ※権限(自治体×機能×操作)も同画面で設定する */ public function edit($id, Request $request) { $ope = Ope::getByPk($id); if (!$ope) abort(404); + // ※機能(画面)一覧を取得(プルダウン用) + $features = Feature::query() + ->orderBy('id') + ->get(['id', 'name']); + + // ※操作権限一覧を取得(チェックボックス用) + $permissions = Permission::query() + ->orderBy('id') + ->get(['id', 'code', 'name']); + + // ※自治体ID(opeに紐づく想定) + $municipalityId = (int)($ope->municipality_id ?? 0); + if ($request->isMethod('get')) { return view('admin.opes.edit', [ 'isEdit' => true, 'record' => $ope, + + // ▼追加:権限設定UI用 + 'features' => $features, + 'permissions' => $permissions, + 'selectedFeatureId' => old('feature_id', null), ]); } + /** + * ▼権限設定の保存(feature_id + permission_ids[]) + * ※画面側の保存ボタンを「権限も同時保存」にする場合はここで処理する + * ※もし「基本情報の更新」と「権限更新」をボタンで分けたい場合は、別アクションに分離推奨 + */ + if ($request->has('feature_id')) { + $request->validate([ + 'feature_id' => ['required', 'integer', 'exists:features,id'], + 'permission_ids' => ['nullable', 'array'], + 'permission_ids.*' => ['integer', 'exists:permissions,id'], + ]); + + $featureId = (int)$request->input('feature_id'); + $permissionIds = array_map('intval', (array)$request->input('permission_ids', [])); + + DB::transaction(function () use ($municipalityId, $featureId, $permissionIds) { + // ※機能単位で置換(自治体単位) + OpePermission::replaceByFeature($municipalityId, $featureId, $permissionIds); + }); + } + // 入力値を一旦取得 $data = $request->all(); // --- バリデーション --- $rules = [ - 'login_id' => "required|string|max:255|unique:ope,login_id,{$id},ope_id", // 編集時は自分を除外 + 'login_id' => "required|string|max:255|unique:ope,login_id,{$id},ope_id", 'ope_name' => 'required|string|max:255', 'ope_type' => 'required|string|max:50', 'ope_phone' => 'nullable|string|max:50', 'ope_mail' => [ 'required', function ($attribute, $value, $fail) { - // , でも ; でもOKにする $emails = array_map('trim', explode(';', str_replace(',', ';', $value))); foreach ($emails as $email) { if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) { @@ -134,7 +188,7 @@ class OpeController extends Controller } } ], - 'password' => 'nullable|string|min:8|confirmed', // 編集時は任意 + 'password' => 'nullable|string|min:8|confirmed', ]; $request->validate($rules); @@ -157,18 +211,42 @@ class OpeController extends Controller return redirect()->route('opes')->with('success', '更新しました。'); } + /** + * 権限回顧(AJAX) + * /opes/{id}/permissions?feature_id=xx + * ※ope_permissionが自治体単位のため、opeの自治体IDで取得する + */ + public function getPermissionsByFeature(int $id, Request $request) + { + $ope = Ope::getByPk($id); + if (!$ope) abort(404); + + $featureId = (int)$request->query('feature_id'); + if ($featureId <= 0) { + return response()->json([]); + } + + $municipalityId = (int)($ope->municipality_id ?? 0); + + $ids = OpePermission::query() + ->where('municipality_id', $municipalityId) + ->where('feature_id', $featureId) + ->pluck('permission_id') + ->values(); + + return response()->json($ids); + } /** * 削除(単体 or 複数) */ - public function delete(Request $request) { $ids = []; // 単体削除 if ($request->filled('id')) { - $ids[] = (int) $request->input('id'); + $ids[] = (int)$request->input('id'); } // 複数削除 diff --git a/app/Http/Controllers/Admin/ParkingRegulationsController.php b/app/Http/Controllers/Admin/ParkingRegulationsController.php new file mode 100644 index 0000000..af26ce6 --- /dev/null +++ b/app/Http/Controllers/Admin/ParkingRegulationsController.php @@ -0,0 +1,204 @@ +validate([ + 'park_id' => 'required|integer|exists:park,park_id', + ], [ + 'park_id.required' => '駐輪場IDは必須です。', + 'park_id.integer' => '駐輪場IDは整数である必要があります。', + 'park_id.exists' => '指定された駐輪場が見つかりません。', + ]); + + $parkId = (int) $request->input('park_id'); + + // 駐輪場情報取得 + $park = Park::where('park_id', $parkId)->firstOrFail(); + + // parking_regulations を取得し、psection / ptype 名を JOIN して表示 + $data = DB::table('parking_regulations') + ->leftJoin('psection', 'parking_regulations.psection_id', '=', 'psection.psection_id') + ->leftJoin('ptype', 'parking_regulations.ptype_id', '=', 'ptype.ptype_id') + ->where('parking_regulations.park_id', $parkId) + ->select( + 'parking_regulations.parking_regulations_seq', + 'parking_regulations.park_id', + 'parking_regulations.psection_id', + 'parking_regulations.ptype_id', + 'parking_regulations.regulations_text', + 'psection.psection_subject as psection_subject', + 'ptype.ptype_subject as ptype_subject' + ) + ->orderBy('parking_regulations.psection_id') + ->orderBy('parking_regulations.ptype_id') + ->paginate(50); + + return view('admin.parking_regulations.list', [ + 'park' => $park, + 'parkId' => $parkId, + 'regulations' => $data, + ]); + } + + /** + * 新規作成フォーム表示/登録 + */ + public function add(Request $request) + { + $parkId = $request->input('park_id'); + + // 駐輪場存在確認 + if (!$parkId) { + return redirect()->back()->withErrors(['park_id' => '駐輪場IDが指定されていません。']); + } + + $park = Park::where('park_id', $parkId)->firstOrFail(); + + // マスタの選択肢取得 + $psections = DB::table('psection')->orderBy('psection_id')->get(); + $ptypes = DB::table('ptype')->orderBy('ptype_id')->get(); + + if ($request->isMethod('post')) { + // 登録処理 + $validated = $request->validate([ + 'park_id' => 'required|integer|exists:park,park_id', + 'psection_id' => 'required|integer', + 'ptype_id' => 'required|integer', + 'regulations_text' => 'nullable|string', + ], [ + 'park_id.required' => '駐輪場IDは必須です。', + 'psection_id.required' => '車種区分は必須です。', + 'ptype_id.required' => '駐輪分類は必須です。', + ]); + + // 重複チェック + $exists = DB::table('parking_regulations') + ->where('park_id', $validated['park_id']) + ->where('psection_id', $validated['psection_id']) + ->where('ptype_id', $validated['ptype_id']) + ->exists(); + + if ($exists) { + return back()->withErrors(['duplicate' => '同じ組み合わせの規定が既に存在します。'])->withInput(); + } + + ParkingRegulation::create([ + 'park_id' => $validated['park_id'], + 'psection_id' => $validated['psection_id'], + 'ptype_id' => $validated['ptype_id'], + 'regulations_text' => $validated['regulations_text'] ?? null, + ]); + + return redirect()->route('parking_regulations_list', ['park_id' => $validated['park_id']])->with('success', '登録しました。'); + } + + return view('admin.parking_regulations.add', [ + 'park' => $park, + 'parkId' => $parkId, + 'psections' => $psections, + 'ptypes' => $ptypes, + ]); + } + + /** + * 編集フォーム表示 + */ + public function edit($seq, Request $request) + { + $record = DB::table('parking_regulations')->where('parking_regulations_seq', $seq)->first(); + if (!$record) { + return redirect()->back()->withErrors(['not_found' => '指定の規定が見つかりません。']); + } + + $park = Park::where('park_id', $record->park_id)->firstOrFail(); + + $psections = DB::table('psection')->orderBy('psection_id')->get(); + $ptypes = DB::table('ptype')->orderBy('ptype_id')->get(); + + return view('admin.parking_regulations.edit', [ + 'park' => $park, + 'record' => $record, + 'psections' => $psections, + 'ptypes' => $ptypes, + ]); + } + + /** + * 更新処理 + */ + public function update($seq, Request $request) + { + $validated = $request->validate([ + 'psection_id' => 'required|integer', + 'ptype_id' => 'required|integer', + 'regulations_text' => 'nullable|string', + ], [ + 'psection_id.required' => '車種区分は必須です。', + 'ptype_id.required' => '駐輪分類は必須です。', + ]); + + // 対象レコード取得 + $record = DB::table('parking_regulations')->where('parking_regulations_seq', $seq)->first(); + if (!$record) { + return back()->withErrors(['not_found' => '指定の規定が見つかりません。']); + } + + // 重複チェック(自分自身は除外) + $exists = DB::table('parking_regulations') + ->where('park_id', $record->park_id) + ->where('psection_id', $validated['psection_id']) + ->where('ptype_id', $validated['ptype_id']) + ->where('parking_regulations_seq', '<>', $seq) + ->exists(); + + if ($exists) { + return back()->withErrors(['duplicate' => '同じ組み合わせの規定が既に存在します。'])->withInput(); + } + + DB::table('parking_regulations')->where('parking_regulations_seq', $seq)->update([ + 'psection_id' => $validated['psection_id'], + 'ptype_id' => $validated['ptype_id'], + 'regulations_text' => $validated['regulations_text'] ?? null, + 'updated_at' => now(), + ]); + + return redirect()->route('parking_regulations_list', ['park_id' => $record->park_id])->with('success', '更新しました。'); + } + + /** + * 削除処理 + */ + public function delete(Request $request) + { + $validated = $request->validate([ + 'parking_regulations_seq' => 'required|integer', + ], [ + 'parking_regulations_seq.required' => '削除対象が指定されていません。', + ]); + + $seq = (int) $validated['parking_regulations_seq']; + + $record = DB::table('parking_regulations')->where('parking_regulations_seq', $seq)->first(); + if (!$record) { + return back()->withErrors(['not_found' => '指定の規定が見つかりません。']); + } + + DB::table('parking_regulations')->where('parking_regulations_seq', $seq)->delete(); + + return redirect()->route('parking_regulations_list', ['park_id' => $record->park_id])->with('success', '削除しました。'); + } +} diff --git a/app/Http/Controllers/Admin/ReductionConfirmMasterController.php b/app/Http/Controllers/Admin/ReductionConfirmMasterController.php new file mode 100644 index 0000000..6c3923a --- /dev/null +++ b/app/Http/Controllers/Admin/ReductionConfirmMasterController.php @@ -0,0 +1,120 @@ +validate([ + 'park_id' => 'required|integer|exists:park,park_id', + ], [ + 'park_id.required' => '駐輪場IDは必須です。', + 'park_id.integer' => '駐輪場IDは整数である必要があります。', + 'park_id.exists' => '指定された駐輪場が見つかりません。', + ]); + + $parkId = (int) $request->input('park_id'); + + // 駐輪場情報を取得 + $park = Park::where('park_id', $parkId)->firstOrFail(); + + // reduction_confirm を主テーブルとして、usertype と JOIN して一覧を取得 + // WHERE park_id = ? で対象駐輪場のレコードのみ取得 + $reductionData = DB::table('reduction_confirm') + ->leftJoin('usertype', 'reduction_confirm.user_categoryid', '=', 'usertype.user_categoryid') + ->where('reduction_confirm.park_id', $parkId) + ->orderBy('reduction_confirm.user_categoryid', 'asc') + ->select( + 'reduction_confirm.park_id', + 'reduction_confirm.user_categoryid', + 'reduction_confirm.reduction_confirm_type', + 'usertype.usertype_subject1', + 'usertype.usertype_subject2', + 'usertype.usertype_subject3' + ) + ->paginate(50); + + return view('admin.reduction_confirm.list', [ + 'park' => $park, + 'parkId' => $parkId, + 'reductionData' => $reductionData, + ]); + } + + /** + * 減免確認情報を一括更新 + * + * @param Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(Request $request) + { + // バリデーション + $validated = $request->validate([ + 'park_id' => 'required|integer|exists:park,park_id', + 'row_user_categoryid' => 'array', + 'row_user_categoryid.*' => 'integer', + 'reduction_confirm_type' => 'array', + 'reduction_confirm_type.*' => 'in:0,1,2', + ], [ + 'park_id.required' => '駐輪場IDは必須です。', + 'park_id.integer' => '駐輪場IDは整数である必要があります。', + 'park_id.exists' => '指定された駐輪場が見つかりません。', + 'reduction_confirm_type.*.in' => '減免確認種別は0, 1, 2 のいずれかである必要があります。', + ]); + + $parkId = (int) $validated['park_id']; + + // ログイン中のオペレータID取得 + $opeId = auth()->user()->ope_id ?? null; + + // POST された配列は index ベースで来るため、row_user_categoryid のインデックスに合わせてマッピングする + $rowUserCategory = $request->input('row_user_categoryid', []); + $types = $request->input('reduction_confirm_type', []); + + try { + DB::transaction(function () use ($parkId, $rowUserCategory, $types, $opeId) { + foreach ($rowUserCategory as $idx => $userCategoryId) { + if (!isset($types[$idx])) { + continue; + } + $type = (int) $types[$idx]; + + DB::table('reduction_confirm') + ->where('park_id', $parkId) + ->where('user_categoryid', (int) $userCategoryId) + ->update([ + 'reduction_confirm_type' => $type, + 'updated_at' => now(), + 'ope_id' => $opeId, + ]); + } + }); + + return redirect()->route('reduction_confirm_list', ['park_id' => $parkId]) + ->with('success', '減免確認マスタを更新しました。'); + } catch (\Exception $e) { + \Log::error('ReductionConfirm update failed', [ + 'park_id' => $parkId, + 'error' => $e->getMessage(), + ]); + + return back()->withErrors(['error' => '更新に失敗しました。管理者にお問い合わせください。']) + ->withInput(); + } + } +} + diff --git a/app/Http/Controllers/Auth/EmailOtpController.php b/app/Http/Controllers/Auth/EmailOtpController.php new file mode 100644 index 0000000..fad9c6f --- /dev/null +++ b/app/Http/Controllers/Auth/EmailOtpController.php @@ -0,0 +1,138 @@ +otpService = $otpService; + } + + /** + * OTP 入力フォームを表示 + * + * ログイン直後、ユーザーに6桁の OTP コードを入力させるページを表示します + * メールアドレスはマスク表示(例:a***@example.com) + */ + public function show(Request $request) + { + /** @var Ope */ + $user = $request->user(); + + // メールアドレスをマスク(最初の1文字のみ表示) + $maskedEmail = $this->otpService->maskEmail($user->ope_mail); + + // 次の重発までの待機時間 + $resendWaitSeconds = $this->otpService->getResendWaitSeconds($user); + + return view('auth.otp', [ + 'maskedEmail' => $maskedEmail, + 'resendWaitSeconds' => $resendWaitSeconds, + ]); + } + + /** + * OTP コード検証 + * + * ユーザーが入力した6桁のコードを検証します + * + * 成功時:email_otp_verified_at を更新し、ホームページにリダイレクト + * 失敗時:エラーメッセージと共に OTP 入力フォームに戻す + */ + public function verify(Request $request) + { + // 入力値を検証 + $validated = $this->validate($request, [ + 'code' => ['required', 'string', 'size:6', 'regex:/^\d{6}$/'], + ], [ + 'code.required' => 'OTPコードは必須です。', + 'code.size' => 'OTPコードは6桁である必要があります。', + 'code.regex' => 'OTPコードは6桁の数字である必要があります。', + ]); + + /** @var Ope */ + $user = $request->user(); + + // OTP コードを検証 + if ($this->otpService->verify($user, $validated['code'])) { + // 検証成功:ホームページにリダイレクト + return redirect()->intended(route('home')) + ->with('success', 'OTP認証が完了しました。'); + } + + // 検証失敗:エラーメッセージと共に戻す + return back() + ->withInput() + ->with('error', '無効なまたは有効期限切れのOTPコードです。'); + } + + /** + * OTP コード再送 + * + * ユーザーが OTP コード再送をリクエストした場合に実行 + * 60秒以内の連続再送はブロックします + */ + public function resend(Request $request) + { + /** @var Ope */ + $user = $request->user(); + + // 重発可能か確認 + if (!$this->otpService->canResend($user)) { + $waitSeconds = $this->otpService->getResendWaitSeconds($user); + + return back()->with('error', "後 {$waitSeconds} 秒待機してからリクエストしてください。"); + } + + try { + // 新しい OTP コードを発行 + $otpCode = $this->otpService->issue($user); + + // ope_mail はセミコロン区切りで複数アドレスを保持する可能性があるため、最初のアドレスのみ抽出 + $operatorEmails = explode(';', trim($user->ope_mail)); + $primaryEmail = trim($operatorEmails[0] ?? $user->ope_mail); + + Log::info('OTP 再送メール送信開始: ' . $primaryEmail); + + // メール送信 + Mail::to($primaryEmail)->send(new EmailOtpMail( + $otpCode, + $user->name ?? 'ユーザー' + )); + + Log::info('OTP 再送メール送信完了: ' . $primaryEmail); + + return back()->with('success', 'OTPコードを再送信しました。'); + } catch (\Exception $e) { + Log::error('OTP resend error: ' . $e->getMessage(), [ + 'exception' => $e, + 'user_id' => $user->ope_id ?? null, + 'user_email' => $user->ope_mail ?? null, + ]); + + return back()->with('error', 'OTP送信に失敗しました。もう一度お試しください。'); + } + } +} diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index c764940..2b34eb9 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -7,6 +7,8 @@ use Illuminate\Http\Request; use Illuminate\Support\Str; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Log; +use Carbon\Carbon; class ForgotPasswordController extends Controller { @@ -37,25 +39,74 @@ class ForgotPasswordController extends Controller return back()->withErrors(['email' => '該当するユーザーが見つかりません。']); } + // 5分間隔のメール送信制限チェック(最新のトークンを対象) + $lastToken = DB::table('password_reset_tokens') + ->where('ope_mail', $user->ope_mail) + ->orderByDesc('created_at') + ->first(); + if ($lastToken) { + // タイムゾーンを明示的に指定(デフォルトはUTCで解析される可能性がある) + $lastCreatedAt = Carbon::parse($lastToken->created_at, config('app.timezone')); + $now = now(); + + // 経過秒数で判定 + $diffSeconds = $lastCreatedAt->diffInSeconds(now(), false); + $limitSeconds = 5 * 60; // 5分 + if ($diffSeconds < $limitSeconds) { + $remainSeconds = $limitSeconds - $diffSeconds; + // 残り秒を「分」に変換:端数は切り上げ(例:1秒残りでも1分と表示) + $waitMinutes = (int) ceil($remainSeconds / 60); + return back()->withErrors([ + 'email' => "パスワード再設定メールは5分以上の間隔を置いて送信してください。{$waitMinutes}分後に再度お試しください。" + ]); + } + } + // トークン生成 $token = Str::random(60); + // SHA256ハッシュで保存(セキュリティ向上) + $tokenHash = hash('sha256', $token); // トークン保存(既存レコードがあれば更新) DB::table('password_reset_tokens')->updateOrInsert( ['ope_mail' => $user->ope_mail], [ - 'token' => $token, + 'token' => $tokenHash, 'created_at' => now(), ] ); // メール送信 - $resetUrl = url('/reset-password?token=' . $token . '&email=' . urlencode($user->ope_mail)); - Mail::raw("下記URLからパスワード再設定を行ってください。\n\n{$resetUrl}", function ($message) use ($user) { - $message->to($user->ope_mail) - ->subject('パスワード再設定のご案内'); - }); + try { + $resetUrl = url('/reset-password?token=' . $token . '&email=' . urlencode($user->ope_mail)); + + $body = $user->ope_name . " 様\n\n" . + "So-Managerをご利用いただき、ありがとうございます。\n\n" . + "本メールは、パスワード再設定のご依頼を受けてお送りしております。\n\n" . + "以下のURLをクリックし、新しいパスワードを設定してください。\n\n" . + $resetUrl . "\n\n" . + "※このURLの有効期限は、24時間です。\n" . + "※有効期限を過ぎた場合は、再度パスワード再設定手続きを行ってください。\n" . + "※本メールにお心当たりがない場合は、本メールを破棄してください。\n\n" . + "_________________________________\n" . + "So-Manager サポートセンター\n" . + "E-mail : support@so-manager.com\n" . + "URL : https://www.so-manager.com/\n" . + "_________________________________"; + + Mail::raw($body, function ($message) use ($user) { + $message->to($user->ope_mail) + ->from(config('mail.from.address'), config('mail.from.name')) + ->subject('【【So-Manager】パスワード再設定のご案内】'); + }); + } catch (\Throwable $e) { + Log::error('ForgotPassword mail send failed', [ + 'to' => $user->ope_mail, + 'error' => $e->getMessage(), + ]); + return back()->withErrors(['email' => 'メール送信に失敗しました。サーバログを確認してください。']); + } return back()->with('status', 'パスワード再設定メールを送信しました。'); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 9f8bce6..18ed0ba 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -3,8 +3,12 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; +use App\Mail\EmailOtpMail; +use App\Services\EmailOtpService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; @@ -135,6 +139,10 @@ class LoginController extends Controller /** * ログイン成功時のレスポンス * + * OTP認証チェック: + * - 24時間以内に OTP 認証済みの場合:/home にリダイレクト + * - 未認証の場合:OTP メール送信 → /otp にリダイレクト + * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse */ @@ -147,7 +155,45 @@ class LoginController extends Controller // ここで保持する値も login_id(入力名は ope_id のまま) $request->session()->put('login_ope_id', $request->input('ope_id')); - return redirect()->intended($this->redirectTo); + // OTP認証チェック + $otpService = app(EmailOtpService::class); + $user = Auth::user(); + + // 24時間以内に OTP 認証済みの場合 + if ($otpService->isOtpRecent($user)) { + return redirect()->intended($this->redirectTo); + } + + // OTP 未認証の場合:OTP コード発行 → メール送信 → /otp にリダイレクト + try { + $otpCode = $otpService->issue($user); + + // ope_mail はセミコロン区切りで複数アドレスを保持する可能性があるため、最初のアドレスのみ抽出 + $operatorEmails = explode(';', trim($user->ope_mail)); + $primaryEmail = trim($operatorEmails[0] ?? $user->ope_mail); + + Log::info('OTP メール送信開始: ' . $primaryEmail); + + Mail::to($primaryEmail)->send(new EmailOtpMail( + $otpCode, + $user->name ?? 'ユーザー' + )); + + Log::info('OTP メール送信完了: ' . $primaryEmail); + + return redirect()->route('otp.show') + ->with('info', 'OTP認証コードをメール送信しました。'); + } catch (\Exception $e) { + Log::error('OTP issue/send failed: ' . $e->getMessage(), [ + 'exception' => $e, + 'user_id' => $user->ope_id ?? null, + 'user_email' => $user->ope_mail ?? null, + ]); + + // メール送信エラー時は home にリダイレクトするか、カスタムエラーを返す + return redirect($this->redirectTo) + ->with('warning', 'OTP認証メールの送信に失敗しました。'); + } } /** diff --git a/app/Http/Controllers/Auth/PasswordChangeController.php b/app/Http/Controllers/Auth/PasswordChangeController.php new file mode 100644 index 0000000..e8dfd9e --- /dev/null +++ b/app/Http/Controllers/Auth/PasswordChangeController.php @@ -0,0 +1,135 @@ +isPasswordChangeRequired($ope); + + return view('auth.password-change', [ + 'isRequired' => $isRequired, + ]); + } + + /** + * パスワード変更成功画面を表示 + * + * GET /password/change/success + * + * @return \Illuminate\View\View + */ + public function showSuccessPage() + { + return view('auth.password-change-success'); + } + + /** + * パスワード変更処理 + * + * POST /password/change + * + * バリデーション: + * - 当前パスワード:必填、8-64文字、ハッシュ値一致確認 + * - 新パスワード:必填、8-64文字、英数字+記号のみ、当前と異なる + * - 新パスワード確認:必填、新パスワードと一致 + * + * @param \App\Http\Requests\ChangePasswordRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function updatePassword(ChangePasswordRequest $request) + { + // 現在のユーザーを取得 + $ope = Auth::user(); + + // ステップ1:当前パスワードの認証(ハッシュ値の確認) + if (!Hash::check($request->current_password, $ope->ope_pass)) { + // バリデーションエラーとして当前パスワード が正しくないことを返す + throw ValidationException::withMessages([ + 'current_password' => '当前パスワードが正しくありません。', + ]); + } + + // ステップ2:新パスワードが当前パスワードと同一でないか確認 + // FormRequest側でも not_in ルールで確認しているが、ハッシュ値での二重チェック + if (Hash::check($request->password, $ope->ope_pass)) { + throw ValidationException::withMessages([ + 'password' => '新パスワードは当前パスワードと異なる必要があります。', + ]); + } + + // ステップ3:データベース更新 + // パスワードをハッシュ化して更新 + $ope->ope_pass = Hash::make($request->password); + + // パスワード変更時刻を現在時刻に更新 + $ope->ope_pass_changed_at = Carbon::now(); + + // updated_at も自動更新される + $ope->save(); + + // イベント発火:パスワード変更イベント + event(new PasswordReset($ope)); + + // 成功画面へリダイレクト + return redirect()->route('password.change.success'); + } + + /** + * パスワード変更が必須かどうかを判定 + * + * 初回ログイン時(ope_pass_changed_at が NULL)または + * 最後変更から3ヶ月以上経過している場合、TRUE を返す + * + * @param \App\Models\Ope $ope + * @return bool + */ + private function isPasswordChangeRequired($ope): bool + { + // パスワード変更日時が未設定(初回ログイン等) + if (is_null($ope->ope_pass_changed_at)) { + return true; + } + + // パスワード変更から経過日数を計算 + $changedAt = Carbon::parse($ope->ope_pass_changed_at); + $now = Carbon::now(); + + // 3ヶ月以上経過している場合 + if ($now->diffInMonths($changedAt) >= 3) { + return true; + } + + return false; + } +} diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 815e80f..7641fc2 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -14,6 +14,33 @@ class ResetPasswordController extends Controller { $token = $request->query('token'); $email = $request->query('email'); + + // トークンのハッシュ化 + $tokenHash = hash('sha256', $token); + + // トークン・メール・24時間以内の有効性をチェック + $record = DB::table('password_reset_tokens') + ->where('ope_mail', $email) + ->where('token', $tokenHash) + ->first(); + + if (!$record) { + return redirect()->route('forgot_password') + ->withErrors(['email' => 'URLの有効期限(24時間)が切れました。再度お手続きを行ってください。']); + } + + // 24時間チェック + $createdAt = \Carbon\Carbon::parse($record->created_at); + if ($createdAt->addHours(24)->isPast()) { + // 期限切れトークンを削除 + DB::table('password_reset_tokens') + ->where('ope_mail', $email) + ->delete(); + + return redirect()->route('forgot_password') + ->withErrors(['email' => 'URLの有効期限(24時間)が切れました。再度お手続きを行ってください。']); + } + return view('auth.reset-password', compact('token', 'email')); } @@ -25,14 +52,28 @@ class ResetPasswordController extends Controller 'password' => 'required|confirmed|min:8', ]); - // トークンチェック + // トークンのハッシュ化 + $tokenHash = hash('sha256', $request->token); + + // トークン・メール・24時間以内の有効性をチェック $record = DB::table('password_reset_tokens') ->where('ope_mail', $request->email) - ->where('token', $request->token) + ->where('token', $tokenHash) ->first(); if (!$record) { - return back()->withErrors(['email' => '無効なトークンまたはメールアドレスです。']); + return back()->withErrors(['email' => 'URLの有効期限(24時間)が切れました。再度お手続きを行ってください。']); + } + + // 24時間チェック + $createdAt = \Carbon\Carbon::parse($record->created_at); + if ($createdAt->addHours(24)->isPast()) { + // 期限切れトークンを削除 + DB::table('password_reset_tokens') + ->where('ope_mail', $request->email) + ->delete(); + + return back()->withErrors(['email' => 'URLの有効期限(24時間)が切れました。再度お手続きを行ってください。']); } // パスワード更新 @@ -42,11 +83,14 @@ class ResetPasswordController extends Controller } $user->password = Hash::make($request->password); $user->updated_at = now(); + // パスワード再設定時もope_pass_changed_atを更新 + $user->ope_pass_changed_at = now(); $user->save(); // トークン削除 DB::table('password_reset_tokens')->where('ope_mail', $request->email)->delete(); - return redirect()->route('login')->with('status', 'パスワードを再設定しました。'); + // パスワード再設定成功画面へリダイレクト + return redirect()->route('password.change.success'); } } \ No newline at end of file diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 8bcdd39..04f7ffe 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -3,6 +3,8 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; +use App\Models\City; +use App\Services\MenuAccessService; class HomeController extends Controller { @@ -22,10 +24,15 @@ class HomeController extends Controller * アプリケーションのダッシュボードを表示 * 認証後のホーム画面 * + * @param MenuAccessService $menuAccessService メニューアクセス制御サービス * @return \Illuminate\Http\Response */ - public function index() + public function index(MenuAccessService $menuAccessService) { - return view('home'); + // ログイン中のオペレータが表示可能な自治体一覧を取得 + $visibleCities = $menuAccessService->visibleCities(); + $isSorin = $menuAccessService->isSorin(); + + return view('home', compact('visibleCities', 'isSorin')); } -} \ No newline at end of file +} diff --git a/app/Http/Middleware/CheckCityAccess.php b/app/Http/Middleware/CheckCityAccess.php new file mode 100644 index 0000000..068283e --- /dev/null +++ b/app/Http/Middleware/CheckCityAccess.php @@ -0,0 +1,34 @@ +route('city_id'); + // $user = auth()->user(); + // if (!$user->canAccessCity($city_id)) { + // return abort(403, '指定された自治体へのアクセス権がありません。'); + // } + + return $next($request); + } +} diff --git a/app/Http/Middleware/CheckPasswordChangeRequired.php b/app/Http/Middleware/CheckPasswordChangeRequired.php new file mode 100644 index 0000000..1ffcdf8 --- /dev/null +++ b/app/Http/Middleware/CheckPasswordChangeRequired.php @@ -0,0 +1,102 @@ +routeIs('password.change.show', 'password.change.update')) { + return $next($request); + } + + // 現在のユーザーを取得 + $ope = Auth::user(); + + // パスワード変更が必須か判定 + if ($this->isPasswordChangeRequired($ope)) { + return redirect()->route('password.change.show'); + } + + return $next($request); + } + + /** + * パスワード変更が必須かどうかを判定 + * + * 初回ログイン時(ope_pass_changed_at が NULL)または + * 最後変更から3ヶ月以上経過している場合、TRUE を返す + * + * @param \App\Models\Ope $ope + * @return bool + */ + private function isPasswordChangeRequired($ope): bool + { + // パスワード変更日時が未設定(初回ログイン等) + if (is_null($ope->ope_pass_changed_at)) { + \Log::info('Password change required: ope_pass_changed_at is null', [ + 'ope_id' => $ope->ope_id, + ]); + return true; + } + + // パスワード変更から経過日数を計算 + // ope_pass_changed_at は複数のフォーマットに対応 + try { + $changedAt = Carbon::parse($ope->ope_pass_changed_at); + } catch (\Exception $e) { + // パース失敗時は強制変更 + \Log::warning('Failed to parse ope_pass_changed_at', [ + 'ope_id' => $ope->ope_id, + 'value' => $ope->ope_pass_changed_at, + 'error' => $e->getMessage(), + ]); + return true; + } + + $now = Carbon::now(); + + // 3ヶ月以上経過しているか判定 + // diffInMonths は絶対値ではなく符号付きなので、abs() で絶対値を取得 + $monthsDiff = abs($now->diffInMonths($changedAt)); + + \Log::info('Password change check', [ + 'ope_id' => $ope->ope_id, + 'changed_at' => $changedAt->format('Y-m-d H:i:s'), + 'now' => $now->format('Y-m-d H:i:s'), + 'months_diff' => $monthsDiff, + 'is_required' => $monthsDiff >= 3, + ]); + + if ($monthsDiff >= 3) { + return true; + } + + return false; + } +} diff --git a/app/Http/Middleware/EnsureOtpVerified.php b/app/Http/Middleware/EnsureOtpVerified.php new file mode 100644 index 0000000..44f8b75 --- /dev/null +++ b/app/Http/Middleware/EnsureOtpVerified.php @@ -0,0 +1,57 @@ +otpService = $otpService; + } + + /** + * リクエストを処理 + * + * @param Request $request + * @param Closure $next + * @return Response + */ + public function handle(Request $request, Closure $next): Response + { + // ユーザーが認証されていない場合はスキップ + if (!$request->user()) { + return $next($request); + } + + // OTP ページ関連のリクエストはスキップ(無限ループ防止) + // /otp /* のパターンを許可 + if ($request->routeIs(['otp.show', 'otp.verify', 'otp.resend'])) { + return $next($request); + } + + // 24時間以内に OTP 認証が完了している場合はスキップ + if ($this->otpService->isOtpRecent($request->user())) { + return $next($request); + } + + // OTP 認証が必要な場合は OTP ページにリダイレクト + return redirect()->route('otp.show') + ->with('info', 'セキュリティ確認のため OTP 認証が必要です。'); + } +} diff --git a/app/Http/Middleware/ShareMenuAccessData.php b/app/Http/Middleware/ShareMenuAccessData.php new file mode 100644 index 0000000..bca3c9f --- /dev/null +++ b/app/Http/Middleware/ShareMenuAccessData.php @@ -0,0 +1,90 @@ + $menuAccessService->isSorin(), + 'visibleCities' => $menuAccessService->visibleCities(), + ]; + + // Nav bar に表示される ハード異常・タスク件数を取得 + if (auth()->check()) { + // ハード異常(que_class > 99)かつステータスが未対応(1)または進行中(2) + $hardwareIssues = DB::table('operator_que as oq') + ->leftJoin('user as u', 'oq.user_id', '=', 'u.user_id') + ->leftJoin('park as p', 'oq.park_id', '=', 'p.park_id') + ->select( + 'oq.que_id', 'oq.que_class', 'oq.que_comment', + 'oq.created_at', 'oq.updated_at', 'oq.que_status' + ) + ->where('oq.que_class', '>', 99) + ->whereIn('oq.que_status', [1, 2]) + ->orderBy('oq.created_at', 'DESC') + ->limit(5) + ->get(); + + // タスク(que_class < 99)かつステータスが未対応(1)または進行中(2) + $taskIssues = DB::table('operator_que as oq') + ->leftJoin('user as u', 'oq.user_id', '=', 'u.user_id') + ->leftJoin('park as p', 'oq.park_id', '=', 'p.park_id') + ->select( + 'oq.que_id', 'oq.que_class', 'oq.que_comment', + 'oq.created_at', 'oq.updated_at', 'oq.que_status' + ) + ->where('oq.que_class', '<', 99) + ->whereIn('oq.que_status', [1, 2]) + ->orderBy('oq.created_at', 'DESC') + ->limit(5) + ->get(); + + // ハード異常・タスク件数計算 + $hardCount = DB::table('operator_que') + ->where('que_class', '>', 99) + ->whereIn('que_status', [1, 2]) + ->count(); + + $taskCount = DB::table('operator_que') + ->where('que_class', '<', 99) + ->whereIn('que_status', [1, 2]) + ->count(); + + // 最新のハード異常・タスク日時 + $hardLatest = $hardwareIssues->first()?->created_at; + $taskLatest = $taskIssues->first()?->created_at; + + // Nav bar 関連データをマージ + $viewData = array_merge($viewData, [ + 'hardCount' => $hardCount, + 'hardLatest' => $hardLatest, + 'latestHards' => $hardwareIssues, + 'taskCount' => $taskCount, + 'taskLatest' => $taskLatest, + 'latestTasks' => $taskIssues, + ]); + } + + // すべてのビューでこれらのデータが利用可能 + View::share($viewData); + + return $next($request); + } +} diff --git a/app/Http/Requests/ChangePasswordRequest.php b/app/Http/Requests/ChangePasswordRequest.php new file mode 100644 index 0000000..eea6761 --- /dev/null +++ b/app/Http/Requests/ChangePasswordRequest.php @@ -0,0 +1,97 @@ +check(); + } + + /** + * 入力値の検証ルール + * + * クライアント側:必填(3項目)、長度 8-64、新密码仅半角英数字+记号、新/确认一致 + * サーバー側:当前密码认证(hash check)、新密码不能等于旧密码(CustomRulesで実装) + * + * @return array|string> + */ + public function rules(): array + { + return [ + // 当前パスワード:必填、8-64文字 + 'current_password' => [ + 'required', + 'string', + 'min:8', + 'max:64', + ], + + // 新パスワード:必填、8-64文字、英数字+記号のみ + 'password' => [ + 'required', + 'string', + 'min:8', + 'max:64', + 'regex:/^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};:\'",.<>?\/\\|`~]+$/', // 半角英数字+記号のみ + ], + + // 新パスワード確認:必填、新パスワードと一致 + 'password_confirmation' => [ + 'required', + 'string', + 'same:password', + ], + + // hidden フィールド(フォーム側で出力) + 'updated_at' => 'nullable|date_format:Y-m-d H:i:s', + 'ope_pass_changed_at' => 'nullable|date_format:Y-m-d H:i:s', + ]; + } + + /** + * 属性の表示名 + * + * @return array + */ + public function attributes(): array + { + return [ + 'current_password' => '当前パスワード', + 'password' => '新パスワード', + 'password_confirmation' => '新パスワード確認', + ]; + } + + /** + * 検証エラーメッセージのカスタマイズ + * + * @return array + */ + public function messages(): array + { + return [ + 'current_password.required' => '当前パスワードを入力してください。', + 'current_password.min' => '当前パスワードは8文字以上です。', + 'current_password.max' => '当前パスワードは64文字以下です。', + + 'password.required' => '新パスワードを入力してください。', + 'password.min' => '新パスワードは8文字以上です。', + 'password.max' => '新パスワードは64文字以下です。', + 'password.regex' => '新パスワードは英数字と記号のみ使用できます。', + + 'password_confirmation.required' => '新パスワード確認を入力してください。', + 'password_confirmation.same' => '新パスワードと新パスワード確認は一致する必要があります。', + ]; + } +} diff --git a/app/Mail/EmailOtpMail.php b/app/Mail/EmailOtpMail.php new file mode 100644 index 0000000..c4c0906 --- /dev/null +++ b/app/Mail/EmailOtpMail.php @@ -0,0 +1,69 @@ +otpCode = $otpCode; + $this->operatorName = $operatorName; + } + + /** + * メールのエンベロープ + */ + public function envelope(): Envelope + { + return new Envelope( + subject: 'ログイン確認用OTPコード(有効期限:10分間)' + ); + } + + /** + * メールのコンテンツ + */ + public function content(): Content + { + return new Content( + view: 'emails.otp', + with: [ + 'otpCode' => $this->otpCode, + 'operatorName' => $this->operatorName, + ] + ); + } + + /** + * メールの添付ファイル + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/City.php b/app/Models/City.php index ccfbd51..080f8d7 100644 --- a/app/Models/City.php +++ b/app/Models/City.php @@ -16,6 +16,7 @@ class City extends Model 'print_layout', 'city_user', 'city_remarks', + 'management_id', 'created_at', 'updated_at', ]; @@ -26,11 +27,40 @@ class City extends Model public static function getList(?int $operatorId = null): array { return static::query() - ->when($operatorId, fn ($q) => $q->where('operator_id', $operatorId)) + ->when($operatorId, fn($q) => $q->where('operator_id', $operatorId)) ->orderBy('city_name') ->pluck('city_name', 'city_id') ->toArray(); } - - -} + + /** + * この都市が属する運営元を取得 + */ + public function management(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Management::class, 'management_id', 'management_id'); + } + + /** + * 自治体別ダッシュボード画面の表示 + * + * 指定された city_id に基づいて都市情報を取得し、 + * 該当データが存在しない場合は 404 エラーを返します。 + * 正常に取得できた場合は、ダッシュボード画面を表示します。 + * + * @param int $city_id 都市ID + * @return \Illuminate\View\View + */ + public function dashboard($city_id) + { + $city = City::find($city_id); + if (!$city) { + abort(404); + } + + // ここに自治体別ダッシュボードの処理を書く + return view('admin.CityMaster.dashboard', [ + 'city' => $city, + ]); + } +} diff --git a/app/Models/Feature.php b/app/Models/Feature.php new file mode 100644 index 0000000..a1cb981 --- /dev/null +++ b/app/Models/Feature.php @@ -0,0 +1,23 @@ +belongsTo(Management::class, 'management_id', 'management_id'); + } } \ No newline at end of file diff --git a/app/Models/OpePermission.php b/app/Models/OpePermission.php new file mode 100644 index 0000000..2382ac2 --- /dev/null +++ b/app/Models/OpePermission.php @@ -0,0 +1,65 @@ +where('municipality_id', $municipalityId) + ->where('feature_id', $featureId) + ->delete(); + + // ※新規追加 + $permissionIds = array_values(array_unique(array_map('intval', $permissionIds))); + foreach ($permissionIds as $pid) { + self::create([ + 'municipality_id' => $municipalityId, + 'feature_id' => $featureId, + 'permission_id' => $pid, + ]); + } + } + + /** + * 付与済み権限ID一覧を取得(自治体単位) + */ + public static function getPermissionIds( + int $municipalityId, + int $featureId + ): array { + return self::query() + ->where('municipality_id', $municipalityId) + ->where('feature_id', $featureId) + ->pluck('permission_id') + ->map(fn ($v) => (int)$v) + ->toArray(); + } +} diff --git a/app/Models/ParkingRegulation.php b/app/Models/ParkingRegulation.php new file mode 100644 index 0000000..60308a1 --- /dev/null +++ b/app/Models/ParkingRegulation.php @@ -0,0 +1,30 @@ +where('code', $code)->first(['id']); + return $row?->id; + } +} diff --git a/app/Models/ReductionMaster.php b/app/Models/ReductionMaster.php new file mode 100644 index 0000000..e199364 --- /dev/null +++ b/app/Models/ReductionMaster.php @@ -0,0 +1,48 @@ +where('user_categoryid', $userCategoryId) + ->first(); + } + + /** + * レコード保存時に operator_id を自動設定 + */ + public static function boot() + { + parent::boot(); + self::saving(function (ReductionMaster $model) { + if (!isset($model->operator_id) || $model->operator_id === null) { + $model->operator_id = Auth::user()->ope_id ?? null; + } + }); + } +} diff --git a/app/Models/management.php b/app/Models/management.php new file mode 100644 index 0000000..33b71dd --- /dev/null +++ b/app/Models/management.php @@ -0,0 +1,62 @@ + 'integer', + 'municipality_flag' => 'integer', + 'government_approval_required' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** 自治体かどうかを判定 */ + public function isMunicipality(): bool + { + return (int)$this->municipality_flag === 1; + } + + /** 役所承認が必要かどうか */ + public function requiresGovernmentApproval(): bool + { + return (int)$this->government_approval_required === 1; + } + + /** + * この運営元に属する自治体を取得 + */ + public function cities(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(City::class, 'management_id', 'management_id'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 8d51665..85279e3 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -9,6 +9,7 @@ use App\Services\ShjNineService; use App\Services\ShjTenService; use App\Services\ShjSixService; use Illuminate\Support\Facades\DB; +use App\Services\MenuAccessService; class AppServiceProvider extends ServiceProvider { @@ -109,10 +110,15 @@ class AppServiceProvider extends ServiceProvider ->limit(5) ->get(); + $menuAccessService = app(MenuAccessService::class); + $isSorin = $menuAccessService->isSorin(); + $visibleCities = $menuAccessService->visibleCities(); + $view->with(compact( 'taskCount','taskLatest', 'hardCount','hardLatest', - 'latestTasks','latestHards' + 'latestTasks','latestHards', + 'isSorin', 'visibleCities' )); }); } diff --git a/app/Services/EmailOtpService.php b/app/Services/EmailOtpService.php new file mode 100644 index 0000000..6ecf05f --- /dev/null +++ b/app/Services/EmailOtpService.php @@ -0,0 +1,148 @@ +email_otp_code_hash = Hash::make($plainCode); + $ope->email_otp_expires_at = now()->addMinutes(self::OTP_VALIDITY_MINUTES); + $ope->email_otp_last_sent_at = now(); + $ope->save(); + + return $plainCode; + } + + /** + * OTP コードを検証 + * + * 入力されたコードが正しく、且つ有効期限内かを確認します + * + * @param Ope $ope オペレータモデル + * @param string $inputCode ユーザーが入力したコード + * @return bool 検証結果 + */ + public function verify(Ope $ope, string $inputCode): bool + { + // 有効期限チェック + if (!$ope->email_otp_expires_at || $ope->email_otp_expires_at < now()) { + return false; + } + + // hash化されたコードと比較 + if (!Hash::check($inputCode, $ope->email_otp_code_hash)) { + return false; + } + + // 検証成功:検証完了時刻を記録し、OTPコードをクリア + $ope->email_otp_verified_at = now(); + $ope->email_otp_code_hash = null; + $ope->email_otp_expires_at = null; + $ope->save(); + + return true; + } + + /** + * OTP が最近検証されたかを確認(24時間以内) + * + * @param Ope $ope オペレータモデル + * @return bool true: 24時間以内に検証済み、false: 未検証または24時間以上経過 + */ + public function isOtpRecent(Ope $ope): bool + { + if (!$ope->email_otp_verified_at) { + return false; + } + + return $ope->email_otp_verified_at > now()->subHours(self::OTP_FREE_PERIOD_HOURS); + } + + /** + * 重発可能かを確認 + * + * 前回送信から60秒以上経過しているかを確認します + * + * @param Ope $ope オペレータモデル + * @return bool true: 重発可能、false: 待機中 + */ + public function canResend(Ope $ope): bool + { + if (!$ope->email_otp_last_sent_at) { + return true; + } + + return $ope->email_otp_last_sent_at <= now()->subSeconds(self::OTP_RESEND_DELAY_SECONDS); + } + + /** + * 次の重発までの待機時間を取得(秒) + * + * @param Ope $ope オペレータモデル + * @return int 待機時間(秒)、0以下は重発可能 + */ + public function getResendWaitSeconds(Ope $ope): int + { + if (!$ope->email_otp_last_sent_at) { + return 0; + } + + $diffSeconds = now()->diffInSeconds($ope->email_otp_last_sent_at, absolute: false); + $waitSeconds = self::OTP_RESEND_DELAY_SECONDS - abs($diffSeconds); + + return max(0, $waitSeconds); + } + + /** + * OTP コードのマスク済みメールアドレスを取得 + * + * abc@example.com -> abc***@example.com のようなマスク表示用 + * + * @param string $email メールアドレス + * @return string マスク済みメールアドレス + */ + public function maskEmail(string $email): string + { + [$name, $domain] = explode('@', $email); + + // 名前部分の最初の1文字を残して、残りは*でマスク + $maskedName = substr($name, 0, 1) . str_repeat('*', max(0, strlen($name) - 1)); + + return "{$maskedName}@{$domain}"; + } +} diff --git a/app/Services/MenuAccessService.php b/app/Services/MenuAccessService.php new file mode 100644 index 0000000..74e5557 --- /dev/null +++ b/app/Services/MenuAccessService.php @@ -0,0 +1,133 @@ +getAuthOperator(); + if (!$operator || !isset($operator->management_id)) { + return null; + } + + return Management::find($operator->management_id); + } + + /** + * ログイン中のオペレータがソーリンであるか判定 + * + * ソーリン(management_name === 'ソーリン')の場合は全メニュー表示可能 + * + * @return bool true: ソーリン, false: その他 + */ + public function isSorin(): bool + { + $management = $this->getAuthManagement(); + if (!$management) { + return false; + } + + return $management->management_name === 'ソーリン'; + } + + /** + * オペレータが表示可能な自治体リストを取得 + * + * ソーリン: 全自治体(削除フラグなどは既存仕様に合わせる) + * その他: ope.city_id で指定された1つの自治体のみ + * + * @return Collection + */ + public function visibleCities(): Collection + { + if ($this->isSorin()) { + return City::query() + ->orderBy('city_name') + ->get(); + } + + // 非ソーリン時は ope.city_id で指定された自治体のみ + $operator = $this->getAuthOperator(); + if (!$operator || !isset($operator->city_id)) { + return collect(); + } + + // ope.city_id に一致する city のみを返す + $city = City::find($operator->city_id); + if (!$city) { + return collect(); + } + + return collect([$city]); + } + + /** + * 指定された自治体へのアクセスが許可されているか判定 + * + * ソーリン: 常に true + * その他: city.management_id == ope.management_id の場合のみ true + * + * @param int $cityId 自治体ID + * @return bool true: アクセス許可, false: アクセス拒否 + */ + public function canAccessCity(int $cityId): bool + { + if ($this->isSorin()) { + return true; + } + + $operator = $this->getAuthOperator(); + if (!$operator || !isset($operator->management_id)) { + return false; + } + + // city.management_id が ope.management_id と一致するか確認 + $city = City::find($cityId); + if (!$city) { + return false; + } + + return (int)$city->management_id === (int)$operator->management_id; + } + + /** + * 複数の自治体へのアクセス許可を一括確認 + * + * @param array $cityIds 自治体IDの配列 + * @return bool すべての自治体へのアクセスが許可されている場合のみ true + */ + public function canAccessCities(array $cityIds): bool + { + foreach ($cityIds as $cityId) { + if (!$this->canAccessCity($cityId)) { + return false; + } + } + + return true; + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 8a80778..52ac71d 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -17,6 +17,16 @@ return Application::configure(basePath: dirname(__DIR__)) '/shj4a', // SHJ-4A本番用エンドポイント '/webhook/wellnet', // SHJ-4A開発・デバッグ用エンドポイント ]); + + // グローバルミドルウェア登録(すべてのリクエストに適用) + $middleware->append(\App\Http\Middleware\ShareMenuAccessData::class); + + // ミドルウェアエイリアス登録 + $middleware->alias([ + 'check.city.access' => \App\Http\Middleware\CheckCityAccess::class, + 'ensure.otp.verified' => \App\Http\Middleware\EnsureOtpVerified::class, + 'check.password.change.required' => \App\Http\Middleware\CheckPasswordChangeRequired::class, + ]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..943eeca --- /dev/null +++ b/config/view.php @@ -0,0 +1,50 @@ + [ + resource_path('views'), + ], + + /* + |-------------------------------------------------------------------------- + | Compiled View Path + |-------------------------------------------------------------------------- + | + | This option determines where all the compiled Blade templates will be + | stored for your application. Typically, this is within the storage + | directory. However, as usual, you are free to change this value. + | + */ + + 'compiled' => env( + 'VIEW_COMPILED_PATH', + realpath(storage_path('framework/views')) + ), + + /* + |-------------------------------------------------------------------------- + | Blade Namespace Paths + |-------------------------------------------------------------------------- + | + | Define custom namespace paths for views. This allows you to organize + | views into different directories and reference them using namespace syntax. + | + */ + + 'namespaces' => [ + 'mail' => resource_path('views/emails'), + ], + +]; diff --git a/public/plugins/summernote/font/summernote.ttf b/public/plugins/summernote/font/summernote.ttf new file mode 100644 index 0000000..b8266cd Binary files /dev/null and b/public/plugins/summernote/font/summernote.ttf differ diff --git a/public/plugins/summernote/font/summernote.woff2 b/public/plugins/summernote/font/summernote.woff2 new file mode 100644 index 0000000..d3a8164 Binary files /dev/null and b/public/plugins/summernote/font/summernote.woff2 differ diff --git a/public/plugins/summernote/summernote.min.css b/public/plugins/summernote/summernote.min.css new file mode 100644 index 0000000..3a70f52 --- /dev/null +++ b/public/plugins/summernote/summernote.min.css @@ -0,0 +1 @@ +@font-face{font-display:auto;font-family:summernote;font-style:normal;font-weight:400;src:url(font/summernote.eot?#iefix) format("embedded-opentype"),url(font/summernote.woff2) format("woff2"),url(font/summernote.woff) format("woff"),url(font/summernote.ttf) format("truetype")}[class*=" note-icon"]:before,[class^=note-icon]:before{display:inline-block;font-family:summernote;font-size:inherit;font-style:normal;text-decoration:inherit;text-rendering:auto;text-transform:none;vertical-align:middle;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;speak:none}.note-icon-fw{text-align:center;width:1.25em}.note-icon-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.note-icon-pull-left{float:left}.note-icon-pull-right{float:right}.note-icon.note-icon-pull-left{margin-right:.3em}.note-icon.note-icon-pull-right{margin-left:.3em}.note-icon-align:before{content:"\ea01"}.note-icon-align-center:before{content:"\ea02"}.note-icon-align-indent:before{content:"\ea03"}.note-icon-align-justify:before{content:"\ea04"}.note-icon-align-left:before{content:"\ea05"}.note-icon-align-outdent:before{content:"\ea06"}.note-icon-align-right:before{content:"\ea07"}.note-icon-arrow-circle-down:before{content:"\ea08"}.note-icon-arrow-circle-left:before{content:"\ea09"}.note-icon-arrow-circle-right:before{content:"\ea0a"}.note-icon-arrow-circle-up:before{content:"\ea0b"}.note-icon-arrows-alt:before{content:"\ea0c"}.note-icon-arrows-h:before{content:"\ea0d"}.note-icon-arrows-v:before{content:"\ea0e"}.note-icon-bold:before{content:"\ea0f"}.note-icon-caret:before{content:"\ea10"}.note-icon-chain-broken:before{content:"\ea11"}.note-icon-circle:before{content:"\ea12"}.note-icon-close:before{content:"\ea13"}.note-icon-code:before{content:"\ea14"}.note-icon-col-after:before{content:"\ea15"}.note-icon-col-before:before{content:"\ea16"}.note-icon-col-remove:before{content:"\ea17"}.note-icon-eraser:before{content:"\ea18"}.note-icon-float-left:before{content:"\ea19"}.note-icon-float-none:before{content:"\ea1a"}.note-icon-float-right:before{content:"\ea1b"}.note-icon-font:before{content:"\ea1c"}.note-icon-frame:before{content:"\ea1d"}.note-icon-italic:before{content:"\ea1e"}.note-icon-link:before{content:"\ea1f"}.note-icon-magic:before{content:"\ea20"}.note-icon-menu-check:before{content:"\ea21"}.note-icon-minus:before{content:"\ea22"}.note-icon-orderedlist:before{content:"\ea23"}.note-icon-pencil:before{content:"\ea24"}.note-icon-picture:before{content:"\ea25"}.note-icon-question:before{content:"\ea26"}.note-icon-redo:before{content:"\ea27"}.note-icon-rollback:before{content:"\ea28"}.note-icon-row-above:before{content:"\ea29"}.note-icon-row-below:before{content:"\ea2a"}.note-icon-row-remove:before{content:"\ea2b"}.note-icon-special-character:before{content:"\ea2c"}.note-icon-square:before{content:"\ea2d"}.note-icon-strikethrough:before{content:"\ea2e"}.note-icon-subscript:before{content:"\ea2f"}.note-icon-summernote:before{content:"\ea30"}.note-icon-superscript:before{content:"\ea31"}.note-icon-table:before{content:"\ea32"}.note-icon-text-height:before{content:"\ea33"}.note-icon-trash:before{content:"\ea34"}.note-icon-underline:before{content:"\ea35"}.note-icon-undo:before{content:"\ea36"}.note-icon-unorderedlist:before{content:"\ea37"}.note-icon-video:before{content:"\ea38"}.note-editor{position:relative}.note-editor .note-dropzone{background-color:#fff;color:#87cefa;display:none;opacity:.95;position:absolute;z-index:100}.note-editor .note-dropzone .note-dropzone-message{display:table-cell;font-size:28px;font-weight:700;text-align:center;vertical-align:middle}.note-editor .note-dropzone.hover{color:#098ddf}.note-editor.dragover .note-dropzone{display:table}.note-editor .note-editing-area{position:relative}.note-editor .note-editing-area .note-editable{outline:none}.note-editor .note-editing-area .note-editable sup{vertical-align:super}.note-editor .note-editing-area .note-editable sub{vertical-align:sub}.note-editor .note-editing-area .note-editable img.note-float-left{margin-right:10px}.note-editor .note-editing-area .note-editable img.note-float-right{margin-left:10px}.note-editor.note-airframe,.note-editor.note-frame{border:1px solid rgba(0,0,0,.196)}.note-editor.note-airframe.codeview .note-editing-area .note-editable,.note-editor.note-frame.codeview .note-editing-area .note-editable{display:none}.note-editor.note-airframe.codeview .note-editing-area .note-codable,.note-editor.note-frame.codeview .note-editing-area .note-codable{display:block}.note-editor.note-airframe .note-editing-area,.note-editor.note-frame .note-editing-area{overflow:hidden}.note-editor.note-airframe .note-editing-area .note-editable,.note-editor.note-frame .note-editing-area .note-editable{overflow:auto;padding:10px;word-wrap:break-word}.note-editor.note-airframe .note-editing-area .note-editable[contenteditable=false],.note-editor.note-frame .note-editing-area .note-editable[contenteditable=false]{background-color:hsla(0,0%,50%,.114)}.note-editor.note-airframe .note-editing-area .note-codable,.note-editor.note-frame .note-editing-area .note-codable{background-color:#222;border:none;border-radius:0;box-shadow:none;-ms-box-sizing:border-box;box-sizing:border-box;color:#ccc;display:none;font-family:Menlo,Monaco,monospace,sans-serif;font-size:14px;margin-bottom:0;outline:none;padding:10px;resize:none;width:100%}.note-editor.note-airframe.fullscreen,.note-editor.note-frame.fullscreen{left:0;position:fixed;top:0;width:100%!important;z-index:1050}.note-editor.note-airframe.fullscreen .note-resizebar,.note-editor.note-frame.fullscreen .note-resizebar{display:none}.note-editor.note-airframe .note-status-output,.note-editor.note-frame .note-status-output{border:0;border-top:1px solid #e2e2e2;color:#000;display:block;font-size:14px;height:20px;line-height:1.42857143;margin-bottom:0;width:100%}.note-editor.note-airframe .note-status-output:empty,.note-editor.note-frame .note-status-output:empty{border-top:0 solid transparent;height:0}.note-editor.note-airframe .note-status-output .pull-right,.note-editor.note-frame .note-status-output .pull-right{float:right!important}.note-editor.note-airframe .note-status-output .text-muted,.note-editor.note-frame .note-status-output .text-muted{color:#777}.note-editor.note-airframe .note-status-output .text-primary,.note-editor.note-frame .note-status-output .text-primary{color:#286090}.note-editor.note-airframe .note-status-output .text-success,.note-editor.note-frame .note-status-output .text-success{color:#3c763d}.note-editor.note-airframe .note-status-output .text-info,.note-editor.note-frame .note-status-output .text-info{color:#31708f}.note-editor.note-airframe .note-status-output .text-warning,.note-editor.note-frame .note-status-output .text-warning{color:#8a6d3b}.note-editor.note-airframe .note-status-output .text-danger,.note-editor.note-frame .note-status-output .text-danger{color:#a94442}.note-editor.note-airframe .note-status-output .alert,.note-editor.note-frame .note-status-output .alert{background-color:#f5f5f5;border-radius:0;color:#000;margin:-7px 0 0;padding:7px 10px 2px}.note-editor.note-airframe .note-status-output .alert .note-icon,.note-editor.note-frame .note-status-output .alert .note-icon{margin-right:5px}.note-editor.note-airframe .note-status-output .alert-success,.note-editor.note-frame .note-status-output .alert-success{background-color:#dff0d8!important;color:#3c763d!important}.note-editor.note-airframe .note-status-output .alert-info,.note-editor.note-frame .note-status-output .alert-info{background-color:#d9edf7!important;color:#31708f!important}.note-editor.note-airframe .note-status-output .alert-warning,.note-editor.note-frame .note-status-output .alert-warning{background-color:#fcf8e3!important;color:#8a6d3b!important}.note-editor.note-airframe .note-status-output .alert-danger,.note-editor.note-frame .note-status-output .alert-danger{background-color:#f2dede!important;color:#a94442!important}.note-editor.note-airframe .note-statusbar,.note-editor.note-frame .note-statusbar{background-color:hsla(0,0%,50%,.114);border-bottom-left-radius:4px;border-bottom-right-radius:4px;border-top:1px solid rgba(0,0,0,.196)}.note-editor.note-airframe .note-statusbar .note-resizebar,.note-editor.note-frame .note-statusbar .note-resizebar{cursor:ns-resize;height:9px;padding-top:1px;width:100%}.note-editor.note-airframe .note-statusbar .note-resizebar .note-icon-bar,.note-editor.note-frame .note-statusbar .note-resizebar .note-icon-bar{border-top:1px solid rgba(0,0,0,.196);margin:1px auto;width:20px}.note-editor.note-airframe .note-statusbar.locked .note-resizebar,.note-editor.note-frame .note-statusbar.locked .note-resizebar{cursor:default}.note-editor.note-airframe .note-statusbar.locked .note-resizebar .note-icon-bar,.note-editor.note-frame .note-statusbar.locked .note-resizebar .note-icon-bar{display:none}.note-editor.note-airframe .note-placeholder,.note-editor.note-frame .note-placeholder{padding:10px}.note-editor.note-airframe{border:0}.note-editor.note-airframe .note-editing-area .note-editable{padding:0}.note-popover.popover{display:none;max-width:none}.note-popover.popover .popover-content a{display:inline-block;max-width:200px;overflow:hidden;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.note-popover.popover .arrow{left:20px!important}.note-toolbar{position:relative}.note-editor .note-toolbar,.note-popover .popover-content{margin:0;padding:0 0 5px 5px}.note-editor .note-toolbar>.note-btn-group,.note-popover .popover-content>.note-btn-group{margin-left:0;margin-right:5px;margin-top:5px}.note-editor .note-toolbar .note-btn-group .note-table,.note-popover .popover-content .note-btn-group .note-table{min-width:0;padding:5px}.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker,.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker{font-size:18px}.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-mousecatcher,.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-mousecatcher{cursor:pointer;height:10em;position:absolute!important;width:10em;z-index:3}.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-unhighlighted,.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-unhighlighted{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASAgMAAAAroGbEAAAACVBMVEUAAIj4+Pjp6ekKlAqjAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfYAR0BKhmnaJzPAAAAG0lEQVQI12NgAAOtVatWMTCohoaGUY+EmIkEAEruEzK2J7tvAAAAAElFTkSuQmCC") repeat;height:5em;position:relative!important;width:5em;z-index:1}.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-highlighted,.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-highlighted{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASAgMAAAAroGbEAAAACVBMVEUAAIjd6vvD2f9LKLW+AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfYAR0BKwNDEVT0AAAAG0lEQVQI12NgAAOtVatWMTCohoaGUY+EmIkEAEruEzK2J7tvAAAAAElFTkSuQmCC") repeat;height:1em;position:absolute!important;width:1em;z-index:2}.note-editor .note-toolbar .note-style .dropdown-style blockquote,.note-editor .note-toolbar .note-style .dropdown-style pre,.note-popover .popover-content .note-style .dropdown-style blockquote,.note-popover .popover-content .note-style .dropdown-style pre{margin:0;padding:5px 10px}.note-editor .note-toolbar .note-style .dropdown-style h1,.note-editor .note-toolbar .note-style .dropdown-style h2,.note-editor .note-toolbar .note-style .dropdown-style h3,.note-editor .note-toolbar .note-style .dropdown-style h4,.note-editor .note-toolbar .note-style .dropdown-style h5,.note-editor .note-toolbar .note-style .dropdown-style h6,.note-editor .note-toolbar .note-style .dropdown-style p,.note-popover .popover-content .note-style .dropdown-style h1,.note-popover .popover-content .note-style .dropdown-style h2,.note-popover .popover-content .note-style .dropdown-style h3,.note-popover .popover-content .note-style .dropdown-style h4,.note-popover .popover-content .note-style .dropdown-style h5,.note-popover .popover-content .note-style .dropdown-style h6,.note-popover .popover-content .note-style .dropdown-style p{margin:0;padding:0}.note-editor .note-toolbar .note-color-all .note-dropdown-menu,.note-popover .popover-content .note-color-all .note-dropdown-menu{min-width:337px}.note-editor .note-toolbar .note-color .dropdown-toggle,.note-popover .popover-content .note-color .dropdown-toggle{padding-left:5px;width:20px}.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette{display:inline-block;margin:0;width:160px}.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette:first-child,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette:first-child{margin:0 5px}.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-palette-title,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-palette-title{border-bottom:1px solid #eee;font-size:12px;margin:2px 7px;text-align:center}.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-reset,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-select,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-reset,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-select{border-radius:5px;cursor:pointer;font-size:11px;margin:3px;padding:0 3px;width:100%}.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-reset:hover,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-select:hover,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-reset:hover,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-select:hover{background:#eee}.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-row,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-row{height:20px}.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-select-btn,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-select-btn{display:none}.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-holder-custom .note-color-btn,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-holder-custom .note-color-btn{border:1px solid #eee}.note-editor .note-toolbar .note-para .note-dropdown-menu,.note-popover .popover-content .note-para .note-dropdown-menu{min-width:228px;padding:5px}.note-editor .note-toolbar .note-para .note-dropdown-menu>div+div,.note-popover .popover-content .note-para .note-dropdown-menu>div+div{margin-left:5px}.note-editor .note-toolbar .note-dropdown-menu,.note-popover .popover-content .note-dropdown-menu{min-width:160px}.note-editor .note-toolbar .note-dropdown-menu.right,.note-popover .popover-content .note-dropdown-menu.right{left:auto;right:0}.note-editor .note-toolbar .note-dropdown-menu.right:before,.note-popover .popover-content .note-dropdown-menu.right:before{left:auto!important;right:9px}.note-editor .note-toolbar .note-dropdown-menu.right:after,.note-popover .popover-content .note-dropdown-menu.right:after{left:auto!important;right:10px}.note-editor .note-toolbar .note-dropdown-menu.note-check a i,.note-popover .popover-content .note-dropdown-menu.note-check a i{color:#00bfff;visibility:hidden}.note-editor .note-toolbar .note-dropdown-menu.note-check a.checked i,.note-popover .popover-content .note-dropdown-menu.note-check a.checked i{visibility:visible}.note-editor .note-toolbar .note-fontsize-10,.note-popover .popover-content .note-fontsize-10{font-size:10px}.note-editor .note-toolbar .note-color-palette,.note-popover .popover-content .note-color-palette{line-height:1}.note-editor .note-toolbar .note-color-palette div .note-color-btn,.note-popover .popover-content .note-color-palette div .note-color-btn{border:0;border-radius:0;height:20px;margin:0;padding:0;width:20px}.note-editor .note-toolbar .note-color-palette div .note-color-btn:hover,.note-popover .popover-content .note-color-palette div .note-color-btn:hover{transform:scale(1.2);transition:all .2s}.note-modal .modal-dialog{border-radius:5px;box-shadow:0 3px 9px rgba(0,0,0,.5);outline:0}.note-modal .form-group{margin-left:0;margin-right:0}.note-modal .note-modal-form{margin:0}.note-modal .note-image-dialog .note-dropzone{border:4px dashed #d3d3d3;color:#d3d3d3;font-size:30px;line-height:4;margin-bottom:10px;min-height:100px;text-align:center}@-moz-document url-prefix(){.note-modal .note-image-input{height:auto}}.note-placeholder{color:gray;display:none;position:absolute}.note-handle .note-control-selection{border:1px solid #000;display:none;position:absolute}.note-handle .note-control-selection>div{position:absolute}.note-handle .note-control-selection .note-control-selection-bg{background-color:#000;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(opacity=30);filter:alpha(opacity=30);height:100%;-webkit-opacity:.3;-khtml-opacity:.3;-moz-opacity:.3;opacity:.3;width:100%}.note-handle .note-control-selection .note-control-handle,.note-handle .note-control-selection .note-control-holder,.note-handle .note-control-selection .note-control-sizing{border:1px solid #000;height:7px;width:7px}.note-handle .note-control-selection .note-control-sizing{background-color:#000}.note-handle .note-control-selection .note-control-nw{border-bottom:none;border-right:none;left:-5px;top:-5px}.note-handle .note-control-selection .note-control-ne{border-bottom:none;border-left:none;right:-5px;top:-5px}.note-handle .note-control-selection .note-control-sw{border-right:none;border-top:none;bottom:-5px;left:-5px}.note-handle .note-control-selection .note-control-se{bottom:-5px;cursor:se-resize;right:-5px}.note-handle .note-control-selection .note-control-se.note-control-holder{border-left:none;border-top:none;cursor:default}.note-handle .note-control-selection .note-control-selection-info{background-color:#000;border-radius:5px;bottom:0;color:#fff;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(opacity=70);filter:alpha(opacity=70);font-size:12px;margin:5px;-webkit-opacity:.7;-khtml-opacity:.7;-moz-opacity:.7;opacity:.7;padding:5px;right:0}.note-hint-popover{min-width:100px;padding:2px}.note-hint-popover .popover-content{max-height:150px;overflow:auto;padding:3px}.note-hint-popover .popover-content .note-hint-group .note-hint-item{display:block!important;padding:3px}.note-hint-popover .popover-content .note-hint-group .note-hint-item.active,.note-hint-popover .popover-content .note-hint-group .note-hint-item:hover{background-color:#428bca;clear:both;color:#fff;cursor:pointer;display:block;font-weight:400;line-height:1.4;outline:0;text-decoration:none;white-space:nowrap}body .note-fullscreen-body,html .note-fullscreen-body{overflow:hidden!important}.note-editable ol li,.note-editable ul li{list-style-position:inside} \ No newline at end of file diff --git a/public/plugins/summernote/summernote.min.js b/public/plugins/summernote/summernote.min.js new file mode 100644 index 0000000..52e140e --- /dev/null +++ b/public/plugins/summernote/summernote.min.js @@ -0,0 +1,2 @@ +/*! Summernote v0.9.0 | (c) 2013~ Hackerwins and contributors | MIT license */ +!function(t,e){if("object"==typeof exports&&"object"==typeof module)module.exports=e(require("jquery"));else if("function"==typeof define&&define.amd)define(["jquery"],e);else{var o="object"==typeof exports?e(require("jquery")):e(t.jQuery);for(var n in o)("object"==typeof exports?exports:t)[n]=o[n]}}(self,(t=>(()=>{"use strict";var e={7e3:(t,e,o)=>{var n=o(8938),i=o.n(n);i().summernote=i().summernote||{lang:{}},i().extend(!0,i().summernote.lang,{"en-US":{font:{bold:"Bold",italic:"Italic",underline:"Underline",clear:"Remove Font Style",height:"Line Height",name:"Font Family",strikethrough:"Strikethrough",subscript:"Subscript",superscript:"Superscript",size:"Font Size",sizeunit:"Font Size Unit"},image:{image:"Picture",insert:"Insert Image",resizeFull:"Resize full",resizeHalf:"Resize half",resizeQuarter:"Resize quarter",resizeNone:"Original size",floatLeft:"Float Left",floatRight:"Float Right",floatNone:"Remove float",shapeRounded:"Shape: Rounded",shapeCircle:"Shape: Circle",shapeThumbnail:"Shape: Thumbnail",shapeNone:"Shape: None",dragImageHere:"Drag image or text here",dropImage:"Drop image or Text",selectFromFiles:"Select from files",maximumFileSize:"Maximum file size",maximumFileSizeError:"Maximum file size exceeded.",url:"Image URL",remove:"Remove Image",original:"Original"},video:{video:"Video",videoLink:"Video Link",insert:"Insert Video",url:"Video URL",providers:"(YouTube, Google Drive, Vimeo, Vine, Instagram, DailyMotion, Youku, Peertube)"},link:{link:"Link",insert:"Insert Link",unlink:"Unlink",edit:"Edit",textToDisplay:"Text to display",url:"To what URL should this link go?",openInNewWindow:"Open in new window"},table:{table:"Table",addRowAbove:"Add row above",addRowBelow:"Add row below",addColLeft:"Add column left",addColRight:"Add column right",delRow:"Delete row",delCol:"Delete column",delTable:"Delete table"},hr:{insert:"Insert Horizontal Rule"},style:{style:"Style",p:"Normal",blockquote:"Quote",pre:"Code",h1:"Header 1",h2:"Header 2",h3:"Header 3",h4:"Header 4",h5:"Header 5",h6:"Header 6"},lists:{unordered:"Unordered list",ordered:"Ordered list"},options:{help:"Help",fullscreen:"Full Screen",codeview:"Code View"},paragraph:{paragraph:"Paragraph",outdent:"Outdent",indent:"Indent",left:"Align left",center:"Align center",right:"Align right",justify:"Justify full"},color:{recent:"Recent Color",more:"More Color",background:"Background Color",foreground:"Text Color",transparent:"Transparent",setTransparent:"Set transparent",reset:"Reset",resetToDefault:"Reset to default",cpSelect:"Select"},shortcut:{shortcuts:"Keyboard shortcuts",close:"Close",textFormatting:"Text formatting",action:"Action",paragraphFormatting:"Paragraph formatting",documentStyle:"Document Style",extraKeys:"Extra keys"},help:{escape:"Escape",insertParagraph:"Insert Paragraph",undo:"Undo the last command",redo:"Redo the last command",tab:"Tab",untab:"Untab",bold:"Set a bold style",italic:"Set a italic style",underline:"Set a underline style",strikethrough:"Set a strikethrough style",removeFormat:"Clean a style",justifyLeft:"Set left align",justifyCenter:"Set center align",justifyRight:"Set right align",justifyFull:"Set full align",insertUnorderedList:"Toggle unordered list",insertOrderedList:"Toggle ordered list",outdent:"Outdent on current paragraph",indent:"Indent on current paragraph",formatPara:"Change current block's format as a paragraph(P tag)",formatH1:"Change current block's format as H1",formatH2:"Change current block's format as H2",formatH3:"Change current block's format as H3",formatH4:"Change current block's format as H4",formatH5:"Change current block's format as H5",formatH6:"Change current block's format as H6",insertHorizontalRule:"Insert horizontal rule","linkDialog.show":"Show Link Dialog"},history:{undo:"Undo",redo:"Redo"},specialChar:{specialChar:"SPECIAL CHARACTERS",select:"Select Special characters"},output:{noSelection:"No Selection Made!"}}})},8938:e=>{e.exports=t}},o={};function n(t){var i=o[t];if(void 0!==i)return i.exports;var r=o[t]={exports:{}};return e[t](r,r.exports,n),r.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var i=n(8938),r=n.n(i),a=(n(7e3),["sans-serif","serif","monospace","cursive","fantasy"]);function s(t){return-1===r().inArray(t.toLowerCase(),a)?"'".concat(t,"'"):t}var l,c=navigator.userAgent,u=/MSIE|Trident/i.test(c);if(u){var d=/MSIE (\d+[.]\d+)/.exec(c);d&&(l=parseFloat(d[1])),(d=/Trident\/.*rv:([0-9]{1,}[.0-9]{0,})/.exec(c))&&(l=parseFloat(d[1]))}var f=/Edge\/\d+/.test(c),h="ontouchstart"in window||navigator.MaxTouchPoints>0||navigator.msMaxTouchPoints>0,p=u?"DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted":"input";const m={isMac:navigator.appVersion.indexOf("Mac")>-1,isMSIE:u,isEdge:f,isFF:!f&&/firefox/i.test(c),isPhantom:/PhantomJS/i.test(c),isWebkit:!f&&/webkit/i.test(c),isChrome:!f&&/chrome/i.test(c),isSafari:!f&&/safari/i.test(c)&&!/chrome/i.test(c),browserVersion:l,isSupportTouch:h,isFontInstalled:function(){var t=document.createElement("canvas"),e=t.getContext("2d",{willReadFrequently:!0});function o(t,o){return e.clearRect(0,0,40,20),e.font="20px "+s(t)+', "'+o+'"',e.fillText("mw",20,10),e.getImageData(0,0,40,20).data.join("")}return t.width=40,t.height=20,e.textAlign="center",e.fillStyle="black",e.textBaseline="middle",function(t){var e="Comic Sans MS"===t?"Courier New":"Comic Sans MS";return o(e,e)!==o(t,e)}}(),isW3CRangeSupport:!!document.createRange,inputEventName:p,genericFontFamilies:a,validFontName:s};var v=0;const g={eq:function(t){return function(e){return t===e}},eq2:function(t,e){return t===e},peq2:function(t){return function(e,o){return e[t]===o[t]}},ok:function(){return!0},fail:function(){return!1},self:function(t){return t},not:function(t){return function(){return!t.apply(t,arguments)}},and:function(t,e){return function(o){return t(o)&&e(o)}},invoke:function(t,e){return function(){return t[e].apply(t,arguments)}},resetUniqueId:function(){v=0},uniqueId:function(t){var e=++v+"";return t?t+e:e},rect2bnd:function(t){var e=r()(document);return{top:t.top+e.scrollTop(),left:t.left+e.scrollLeft(),width:t.right-t.left,height:t.bottom-t.top}},invertObject:function(t){var e={};for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&(e[t[o]]=o);return e},namespaceToCamel:function(t,e){return(e=e||"")+t.split(".").map((function(t){return t.substring(0,1).toUpperCase()+t.substring(1)})).join("")},debounce:function(t,e,o){var n;return function(){var i=this,r=arguments,a=o&&!n;clearTimeout(n),n=setTimeout((function(){n=null,o||t.apply(i,r)}),e),a&&t.apply(i,r)}},isValidUrl:function(t){return/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/gi.test(t)}};function b(t){return t[0]}function y(t){return t[t.length-1]}function k(t){return t.slice(1)}function w(t,e){if(t&&t.length&&e){if(t.indexOf)return-1!==t.indexOf(e);if(t.contains)return t.contains(e)}return!1}const C={head:b,last:y,initial:function(t){return t.slice(0,t.length-1)},tail:k,prev:function(t,e){if(t&&t.length&&e){var o=t.indexOf(e);return-1===o?null:t[o-1]}return null},next:function(t,e){if(t&&t.length&&e){var o=t.indexOf(e);return-1===o?null:t[o+1]}return null},find:function(t,e){for(var o=0,n=t.length;o";function U(t){return T(t)?t.nodeValue.length:t?t.childNodes.length:0}function W(t){var e=U(t);return 0===e||(!T(t)&&1===e&&t.innerHTML===M||!(!C.all(t.childNodes,T)||""!==t.innerHTML))}function K(t){P(t)||U(t)||(t.innerHTML=M)}function q(t,e){for(;t;){if(e(t))return t;if(x(t))break;t=t.parentNode}return null}function V(t,e){e=e||g.fail;var o=[];return q(t,(function(t){return x(t)||o.push(t),e(t)})),o}function _(t,e){e=e||g.fail;for(var o=[];t&&!e(t);)o.push(t),t=t.nextSibling;return o}function G(t,e){var o=e.nextSibling,n=e.parentNode;return o?n.insertBefore(t,o):n.appendChild(t),t}function Z(t,e,o){return r().each(e,(function(e,n){!o&&$(t)&&null===t.firstChild&&F(n)&&t.appendChild(ut("br")),t.appendChild(n)})),t}function Y(t){return 0===t.offset}function X(t){return t.offset===U(t.node)}function Q(t){return Y(t)||X(t)}function J(t,e){for(;t&&t!==e;){if(0!==et(t))return!1;t=t.parentNode}return!0}function tt(t,e){if(!e)return!1;for(;t&&t!==e;){if(et(t)!==U(t.parentNode)-1)return!1;t=t.parentNode}return!0}function et(t){for(var e=0;t=t.previousSibling;)e+=1;return e}function ot(t){return!!(t&&t.childNodes&&t.childNodes.length)}function nt(t,e){var o,n;if(0===t.offset){if(x(t.node))return null;o=t.node.parentNode,n=et(t.node)}else ot(t.node)?n=U(o=t.node.childNodes[t.offset-1]):(o=t.node,n=e?0:t.offset-1);return{node:o,offset:n}}function it(t,e){var o,n;if(U(t.node)===t.offset){if(x(t.node))return null;var i=at(t.node);i?(o=i,n=0):(o=t.node.parentNode,n=et(t.node)+1)}else ot(t.node)?(o=t.node.childNodes[t.offset],n=0):(o=t.node,n=e?U(t.node):t.offset+1);return{node:o,offset:n}}function rt(t,e){var o,n=0;if(U(t.node)===t.offset){if(x(t.node))return null;o=t.node.parentNode,n=et(t.node)+1,x(o)&&(o=t.node.nextSibling,n=0)}else ot(t.node)?(o=t.node.childNodes[t.offset],n=0):(o=t.node,n=e?U(t.node):t.offset+1);return{node:o,offset:n}}function at(t){if(t.nextSibling&&t.parent===t.nextSibling.parent)return T(t.nextSibling)?t.nextSibling:at(t.nextSibling)}function st(t,e){return t.node===e.node&&t.offset===e.offset}function lt(t,e){var o=e&&e.isSkipPaddingBlankHTML,n=e&&e.isNotSplitEdgePoint,i=e&&e.isDiscardEmptySplits;if(i&&(o=!0),Q(t)&&(T(t.node)||n)){if(Y(t))return t.node;if(X(t))return t.node.nextSibling}if(T(t.node))return t.node.splitText(t.offset);var r=_(t.node.childNodes[t.offset]),a=G(t.node.cloneNode(!1),t.node);return Z(a,r),o||(K(t.node),K(a)),i&&(W(t.node)&&dt(t.node),W(a))?(dt(a),t.node.nextSibling):a}function ct(t,e,o){var n=V(e.node,g.eq(t));if(!n.length)return null;if(1===n.length)return lt(e,o);if(n.length>2){var i=n.slice(0,n.length-1).find((function(t){return t.nextSibling}));if(i&&0!=e.offset&&X(e)){var r,a=i.nextSibling;1==a.nodeType?(n=V(r=a.childNodes[0],g.eq(t)),e={node:r,offset:0}):3!=a.nodeType||a.data.match(/[\n\r]/g)||(n=V(r=a,g.eq(t)),e={node:r,offset:0})}}return n.reduce((function(t,n){return t===e.node&&(t=lt(e,o)),lt({node:n,offset:t?et(t):U(n)},o)}))}function ut(t){return document.createElement(t)}function dt(t,e){if(t&&t.parentNode){if(t.removeNode)return t.removeNode(e);var o=t.parentNode;if(!e){for(var n=[],i=0,r=t.childNodes.length;i".concat(M,"

"),makePredByNodeName:E,isEditable:x,isControlSizing:function(t){return t&&r()(t).hasClass("note-control-sizing")},isText:T,isElement:function(t){return t&&1===t.nodeType},isVoid:P,isPara:N,isPurePara:function(t){return N(t)&&!$(t)},isHeading:function(t){return t&&/^H[1-7]/.test(t.nodeName.toUpperCase())},isInline:L,isBlock:g.not(L),isBodyInline:function(t){return L(t)&&!q(t,N)},isBody:B,isParaInline:function(t){return L(t)&&!!q(t,N)},isPre:I,isList:F,isTable:R,isData:A,isCell:H,isBlockquote:j,isBodyContainer:z,isAnchor:O,isDiv:E("DIV"),isLi:$,isBR:E("BR"),isSpan:E("SPAN"),isB:E("B"),isU:E("U"),isS:E("S"),isI:E("I"),isImg:E("IMG"),isTextarea:ft,deepestChildIsEmpty:function(t){do{if(null===t.firstElementChild||""===t.firstElementChild.innerHTML)break}while(t=t.firstElementChild);return W(t)},isEmpty:W,isEmptyAnchor:g.and(O,W),isClosestSibling:function(t,e){return t.nextSibling===e||t.previousSibling===e},withClosestSiblings:function(t,e){e=e||g.ok;var o=[];return t.previousSibling&&e(t.previousSibling)&&o.push(t.previousSibling),o.push(t),t.nextSibling&&e(t.nextSibling)&&o.push(t.nextSibling),o},nodeLength:U,isLeftEdgePoint:Y,isRightEdgePoint:X,isEdgePoint:Q,isLeftEdgeOf:J,isRightEdgeOf:tt,isLeftEdgePointOf:function(t,e){return Y(t)&&J(t.node,e)},isRightEdgePointOf:function(t,e){return X(t)&&tt(t.node,e)},prevPoint:nt,nextPoint:it,nextPointWithEmptyNode:rt,isSamePoint:st,isVisiblePoint:function(t){if(T(t.node)||!ot(t.node)||W(t.node))return!0;var e=t.node.childNodes[t.offset-1],o=t.node.childNodes[t.offset];return!((e&&!P(e)||o&&!P(o))&&!R(o))},prevPointUntil:function(t,e){for(;t;){if(e(t))return t;t=nt(t)}return null},nextPointUntil:function(t,e){for(;t;){if(e(t))return t;t=it(t)}return null},isCharPoint:function(t){if(!T(t.node))return!1;var e=t.node.nodeValue.charAt(t.offset-1);return e&&" "!==e&&e!==S},isSpacePoint:function(t){if(!T(t.node))return!1;var e=t.node.nodeValue.charAt(t.offset-1);return" "===e||e===S},walkPoint:function(t,e,o,n){for(var i=t;i&&i.node&&(o(i),!st(i,e));){i=rt(i,n&&t.node!==i.node&&e.node!==i.node)}},ancestor:q,singleChildAncestor:function(t,e){for(t=t.parentNode;t&&1===U(t);){if(e(t))return t;if(x(t))break;t=t.parentNode}return null},listAncestor:V,lastAncestor:function(t,e){var o=V(t);return C.last(o.filter(e))},listNext:_,listPrev:function(t,e){e=e||g.fail;for(var o=[];t&&!e(t);)o.push(t),t=t.previousSibling;return o},listDescendant:function(t,e){var o=[];return e=e||g.ok,function n(i){t!==i&&e(i)&&o.push(i);for(var r=0,a=i.childNodes.length;r-1)return n;return null},wrap:function(t,e){var o=t.parentNode,n=r()("<"+e+">")[0];return o.insertBefore(n,t),n.appendChild(t),n},insertAfter:G,appendChildNodes:Z,position:et,hasChildren:ot,makeOffsetPath:function(t,e){return V(e,g.eq(t)).map(et).reverse()},fromOffsetPath:function(t,e){for(var o=t,n=0,i=e.length;n\s]*)(.*?)(\s*\/?>)/g,(function(t,e,o){o=o.toUpperCase();var n=/^DIV|^TD|^TH|^P|^LI|^H[1-7]/.test(o)&&!!e,i=/^BLOCKQUOTE|^TABLE|^TBODY|^TR|^HR|^UL|^OL/.test(o);return t+(n||i?"\n":"")}))).trim()}return o},value:ht,posFromPlaceholder:function(t){var e=r()(t),o=e.offset(),n=e.outerHeight(!0);return{left:o.left,top:o.top+n}},attachEvents:function(t,e){Object.keys(e).forEach((function(o){t.on(o,e[o])}))},detachEvents:function(t,e){Object.keys(e).forEach((function(o){t.off(o,e[o])}))},isCustomStyleTag:function(t){return t&&!T(t)&&C.contains(t.classList,"note-styletag")}};function mt(t){return mt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},mt(t)}function vt(t,e){for(var o=0;o1,i=n&&C.head(o),r=n?C.last(o):C.head(o),a=this.modules[i||"editor"];return!i&&this[r]?this[r].apply(this,e):a&&a[r]&&a.shouldInitialize()?a[r].apply(a,e):void 0}}],e&&vt(t.prototype,e),o&&vt(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function yt(t){return yt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},yt(t)}function kt(t){return kt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},kt(t)}function wt(t,e){for(var o=0;o=0)break;n=a[o]}if(0!==o&&pt.isText(a[o-1])){var s=document.body.createTextRange(),l=null;s.moveToElementText(n||i),s.collapse(!n),l=n?n.nextSibling:i.firstChild;var c=t.duplicate();c.setEndPoint("StartToStart",s);for(var u=c.text.replace(/[\r\n]/g,"").length;u>l.nodeValue.length&&l.nextSibling;)u-=l.nodeValue.length,l=l.nextSibling;l.nodeValue;e&&l.nextSibling&&pt.isText(l.nextSibling)&&u===l.nodeValue.length&&(u-=l.nodeValue.length,l=l.nextSibling),i=l,o=u}return{cont:i,offset:o}}function xt(t){var e=document.body.createTextRange(),o=function t(e,o){var n,i;if(pt.isText(e)){var r=pt.listPrev(e,g.not(pt.isText)),a=C.last(r).previousSibling;n=a||e.parentNode,o+=C.sum(C.tail(r),pt.nodeLength),i=!a}else{if(n=e.childNodes[o]||e,pt.isText(n))return t(n,0);o=0,i=!1}return{node:n,collapseToStart:i,offset:o}}(t.node,t.offset);return e.moveToElementText(o.node),e.collapse(o.collapseToStart),e.moveStart("character",o.offset),e}r().fn.extend({summernote:function(){var t=yt(C.head(arguments)),e="string"===t,o="object"===t,n=r().extend({},r().summernote.options,o?C.head(arguments):{});n.langInfo=r().extend(!0,{},r().summernote.lang["en-US"],r().summernote.lang[n.lang]),n.icons=r().extend(!0,{},r().summernote.options.icons,n.icons),n.tooltip="auto"===n.tooltip?!m.isSupportTouch:n.tooltip,this.each((function(t,e){var o=r()(e);if(!o.data("summernote")){var i=new bt(o,n);o.data("summernote",i),o.data("summernote").triggerEvent("init",i.layoutInfo)}}));var i=this.first();if(i.length){var a=i.data("summernote");if(e)return a.invoke.apply(a,C.from(arguments));n.focus&&a.invoke("editor.focus")}return this}});var Et=function(){function t(e,o,n,i){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.sc=e,this.so=o,this.ec=n,this.eo=i,this.isOnEditable=this.makeIsOn(pt.isEditable),this.isOnList=this.makeIsOn(pt.isList),this.isOnAnchor=this.makeIsOn(pt.isAnchor),this.isOnCell=this.makeIsOn(pt.isCell),this.isOnData=this.makeIsOn(pt.isData)}return e=t,o=[{key:"nativeRange",value:function(){if(m.isW3CRangeSupport){var t=document.createRange();return t.setStart(this.sc,this.so),t.setEnd(this.ec,this.eo),t}var e=xt({node:this.sc,offset:this.so});return e.setEndPoint("EndToEnd",xt({node:this.ec,offset:this.eo})),e}},{key:"getPoints",value:function(){return{sc:this.sc,so:this.so,ec:this.ec,eo:this.eo}}},{key:"getStartPoint",value:function(){return{node:this.sc,offset:this.so}}},{key:"getEndPoint",value:function(){return{node:this.ec,offset:this.eo}}},{key:"select",value:function(){var t=this.nativeRange();if(m.isW3CRangeSupport){var e=document.getSelection();e.rangeCount>0&&e.removeAllRanges(),e.addRange(t)}else t.select();return this}},{key:"scrollIntoView",value:function(t){var e=r()(t).height();return t.scrollTop+e0?o.so-1:0];if(e){var i=pt.listPrev(e,pt.isParaInline).reverse();if((i=i.concat(pt.listNext(e.nextSibling,pt.isParaInline))).length){var r=pt.wrap(C.head(i),"p");pt.appendChildNodes(r,C.tail(i))}}return this.normalize()}},{key:"insertNode",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],o=this;(pt.isText(t)||pt.isInline(t))&&(o=this.wrapBodyInlineWithPara().deleteContents());var n=pt.splitPoint(o.getStartPoint(),pt.isInline(t));return n.rightNode?(n.rightNode.parentNode.insertBefore(t,n.rightNode),pt.isEmpty(n.rightNode)&&(e||pt.isPara(t))&&n.rightNode.parentNode.removeChild(n.rightNode)):n.container.appendChild(t),t}},{key:"pasteHTML",value:function(t){t=((t||"")+"").trim(t);var e=r()("
").html(t)[0],o=C.from(e.childNodes),n=this,i=!1;return n.so>=0&&(o=o.reverse(),i=!0),o=o.map((function(t){return n.insertNode(t,!pt.isInline(t))})),i&&(o=o.reverse()),o}},{key:"toString",value:function(){var t=this.nativeRange();return m.isW3CRangeSupport?t.toString():t.text}},{key:"getWordRange",value:function(e){var o=this.getEndPoint();if(!pt.isCharPoint(o))return this;var n=pt.prevPointUntil(o,(function(t){return!pt.isCharPoint(t)}));return e&&(o=pt.nextPointUntil(o,(function(t){return!pt.isCharPoint(t)}))),new t(n.node,n.offset,o.node,o.offset)}},{key:"getWordsRange",value:function(e){var o=this.getEndPoint(),n=function(t){return!pt.isCharPoint(t)&&!pt.isSpacePoint(t)};if(n(o))return this;var i=pt.prevPointUntil(o,n);return e&&(o=pt.nextPointUntil(o,n)),new t(i.node,i.offset,o.node,o.offset)}},{key:"getWordsMatchRange",value:function(e){var o=this.getEndPoint(),n=pt.prevPointUntil(o,(function(n){if(!pt.isCharPoint(n)&&!pt.isSpacePoint(n))return!0;var i=new t(n.node,n.offset,o.node,o.offset),r=e.exec(i.toString());return r&&0===r.index})),i=new t(n.node,n.offset,o.node,o.offset),r=i.toString(),a=e.exec(r);return a&&a[0].length===r.length?i:null}},{key:"bookmark",value:function(t){return{s:{path:pt.makeOffsetPath(t,this.sc),offset:this.so},e:{path:pt.makeOffsetPath(t,this.ec),offset:this.eo}}}},{key:"paraBookmark",value:function(t){return{s:{path:C.tail(pt.makeOffsetPath(C.head(t),this.sc)),offset:this.so},e:{path:C.tail(pt.makeOffsetPath(C.last(t),this.ec)),offset:this.eo}}}},{key:"getClientRects",value:function(){return this.nativeRange().getClientRects()}}],o&&wt(e.prototype,o),n&&wt(e,n),Object.defineProperty(e,"prototype",{writable:!1}),e;var e,o,n}();const Tt={create:function(t,e,o,n){if(4===arguments.length)return new Et(t,e,o,n);if(2===arguments.length)return new Et(t,e,o=t,n=e);var i=this.createFromSelection();if(!i&&1===arguments.length){var r=arguments[0];return pt.isEditable(r)&&(r=r.lastChild),this.createFromBodyElement(r,pt.emptyPara===arguments[0].innerHTML)}return i},createFromBodyElement:function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return this.createFromNode(t).collapse(e)},createFromSelection:function(){var t,e,o,n;if(m.isW3CRangeSupport){var i=document.getSelection();if(!i||0===i.rangeCount)return null;if(pt.isBody(i.anchorNode))return null;var r=i.getRangeAt(0);t=r.startContainer,e=r.startOffset,o=r.endContainer,n=r.endOffset}else{var a=document.selection.createRange(),s=a.duplicate();s.collapse(!1);var l=a;l.collapse(!0);var c=St(l,!0),u=St(s,!1);pt.isText(c.node)&&pt.isLeftEdgePoint(c)&&pt.isTextNode(u.node)&&pt.isRightEdgePoint(u)&&u.node.nextSibling===c.node&&(c=u),t=c.cont,e=c.offset,o=u.cont,n=u.offset}return new Et(t,e,o,n)},createFromNode:function(t){var e=t,o=0,n=t,i=pt.nodeLength(n);return pt.isVoid(e)&&(o=pt.listPrev(e).length-1,e=e.parentNode),pt.isBR(n)?(i=pt.listPrev(n).length-1,n=n.parentNode):pt.isVoid(n)&&(i=pt.listPrev(n).length,n=n.parentNode),this.create(e,o,n,i)},createFromNodeBefore:function(t){return this.createFromNode(t).collapse(!0)},createFromNodeAfter:function(t){return this.createFromNode(t).collapse()},createFromBookmark:function(t,e){var o=pt.fromOffsetPath(t,e.s.path),n=e.s.offset,i=pt.fromOffsetPath(t,e.e.path),r=e.e.offset;return new Et(o,n,i,r)},createFromParaBookmark:function(t,e){var o=t.s.offset,n=t.e.offset,i=pt.fromOffsetPath(C.head(e),t.s.path),r=pt.fromOffsetPath(C.last(e),t.e.path);return new Et(i,o,r,n)}};var Pt={BACKSPACE:8,TAB:9,ENTER:13,ESCAPE:27,SPACE:32,DELETE:46,LEFT:37,UP:38,RIGHT:39,DOWN:40,NUM0:48,NUM1:49,NUM2:50,NUM3:51,NUM4:52,NUM5:53,NUM6:54,NUM7:55,NUM8:56,B:66,E:69,I:73,J:74,K:75,L:76,R:82,S:83,U:85,V:86,Y:89,Z:90,SLASH:191,LEFTBRACKET:219,BACKSLASH:220,RIGHTBRACKET:221,HOME:36,END:35,PAGEUP:33,PAGEDOWN:34};const Nt={isEdit:function(t){return C.contains([Pt.BACKSPACE,Pt.TAB,Pt.ENTER,Pt.SPACE,Pt.DELETE],t)},isRemove:function(t){return C.contains([Pt.BACKSPACE,Pt.DELETE],t)},isMove:function(t){return C.contains([Pt.LEFT,Pt.UP,Pt.RIGHT,Pt.DOWN],t)},isNavigation:function(t){return C.contains([Pt.HOME,Pt.END,Pt.PAGEUP,Pt.PAGEDOWN],t)},nameFromCode:g.invertObject(Pt),code:Pt};function It(t){return It="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},It(t)}function $t(t,e){for(var o=0;o0&&(this.stackOffset--,this.applySnapshot(this.stack[this.stackOffset]))}},{key:"redo",value:function(){this.stack.length-1>this.stackOffset&&(this.stackOffset++,this.applySnapshot(this.stack[this.stackOffset]))}},{key:"recordUndo",value:function(){this.stackOffset++,this.stack.length>this.stackOffset&&(this.stack=this.stack.slice(0,this.stackOffset)),this.stack.push(this.makeSnapshot()),this.stack.length>this.context.options.historyLimit&&(this.stack.shift(),this.stackOffset-=1)}}])&&$t(t.prototype,e),o&&$t(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function Lt(t){return Lt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Lt(t)}function Ft(t,e){for(var o=0;o-1;o["list-style"]=n?"unordered":"ordered"}else o["list-style"]="none";var i=pt.ancestor(t.sc,pt.isPara);if(i&&i.style["line-height"])o["line-height"]=i.style.lineHeight;else{var a=parseInt(o["line-height"],10)/parseInt(o["font-size"],10);o["line-height"]=a.toFixed(1)}return o.anchor=t.isOnAnchor()&&pt.ancestor(t.sc,pt.isAnchor),o.ancestors=pt.listAncestor(t.sc,pt.isEditable),o.range=t,o}}],e&&Ft(t.prototype,e),o&&Ft(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function jt(t){return jt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},jt(t)}function zt(t,e){for(var o=0;o25?e-25:""}))}))})),o.select()}},{key:"toggleList",value:function(t,e){var o=this,n=Tt.create(e).wrapBodyInlineWithPara(),i=n.nodes(pt.isPara,{includeAncestor:!0}),a=n.paraBookmark(i),s=C.clusterBy(i,g.peq2("parentNode"));if(C.find(i,pt.isPurePara)){var l=[];r().each(s,(function(e,n){l=l.concat(o.wrapList(n,t))})),i=l}else{var c=n.nodes(pt.isList,{includeAncestor:!0}).filter((function(e){return!r().nodeName(e,t)}));c.length?r().each(c,(function(e,o){pt.replace(o,t)})):i=this.releaseList(s,!0)}Tt.createFromParaBookmark(a,i).select()}},{key:"wrapList",value:function(t,e){var o=C.head(t),n=C.last(t),i=pt.isList(o.previousSibling)&&o.previousSibling,r=pt.isList(n.nextSibling)&&n.nextSibling,a=i||pt.insertAfter(pt.create(e||"UL"),n);return t=t.map((function(t){return pt.isPurePara(t)?pt.replace(t,"LI"):t})),pt.appendChildNodes(a,t,!0),r&&(pt.appendChildNodes(a,C.from(r.childNodes),!0),pt.remove(r)),t}},{key:"releaseList",value:function(t,e){var o=this,n=[];return r().each(t,(function(t,i){var a=C.head(i),s=C.last(i),l=e?pt.lastAncestor(a,pt.isList):a.parentNode,c=l.parentNode;if("LI"===l.parentNode.nodeName)i.map((function(t){var e=o.findNextSiblings(t);c.nextSibling?c.parentNode.insertBefore(t,c.nextSibling):c.parentNode.appendChild(t),e.length&&(o.wrapList(e,l.nodeName),t.appendChild(e[0].parentNode))})),0===l.children.length&&c.removeChild(l),0===c.childNodes.length&&c.parentNode.removeChild(c);else{var u=l.childNodes.length>1?pt.splitTree(l,{node:s.parentNode,offset:pt.position(s)+1},{isSkipPaddingBlankHTML:!0}):null,d=pt.splitTree(l,{node:a.parentNode,offset:pt.position(a)},{isSkipPaddingBlankHTML:!0});i=e?pt.listDescendant(d,pt.isLi):C.from(d.childNodes).filter(pt.isLi),!e&&pt.isList(l.parentNode)||(i=i.map((function(t){return pt.replace(t,"P")}))),r().each(C.from(i).reverse(),(function(t,e){pt.insertAfter(e,l)}));var f=C.compact([l,d,u]);r().each(f,(function(t,e){var o=[e].concat(pt.listDescendant(e,pt.isList));r().each(o.reverse(),(function(t,e){pt.nodeLength(e)||pt.remove(e,!0)}))}))}n=n.concat(i)})),n}},{key:"appendToPrevious",value:function(t){return t.previousSibling?pt.appendChildNodes(t.previousSibling,[t]):this.wrapList([t],"LI")}},{key:"findList",value:function(t){return t?C.find(t.children,(function(t){return["OL","UL"].indexOf(t.nodeName)>-1})):null}},{key:"findNextSiblings",value:function(t){for(var e=[];t.nextSibling;)e.push(t.nextSibling),t=t.nextSibling;return e}}],e&&zt(t.prototype,e),o&&zt(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function Mt(t){return Mt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Mt(t)}function Ut(t,e){for(var o=0;o1,i=e.rowSpan>1,a=t.rowIndex===r.rowPos&&e.cellIndex===r.colPos;l(t.rowIndex,o,t,e,i,n,!1);var s=e.attributes.rowSpan?parseInt(e.attributes.rowSpan.value,10):0;if(s>1)for(var c=1;c1)for(var p=1;p=o.cellIndex&&o.cellIndex<=e&&!n&&r.colPos++}function h(e){switch(o){case t.where.Column:if(e.isColSpan)return t.resultAction.SubtractSpanCount;break;case t.where.Row:if(!e.isVirtual&&e.isRowSpan)return t.resultAction.AddCell;if(e.isRowSpan)return t.resultAction.SubtractSpanCount}return t.resultAction.RemoveCell}function p(e){switch(o){case t.where.Column:if(e.isColSpan)return t.resultAction.SumSpanCount;if(e.isRowSpan&&e.isVirtual)return t.resultAction.Ignore;break;case t.where.Row:if(e.isRowSpan)return t.resultAction.SumSpanCount;if(e.isColSpan&&e.isVirtual)return t.resultAction.Ignore}return t.resultAction.AddCell}this.getActionList=function(){for(var e=o===t.where.Row?r.rowPos:-1,i=o===t.where.Column?r.colPos:-1,l=0,u=!0;u;){var d=e>=0?e:l,f=i>=0?i:l,m=a[d];if(!m)return u=!1,s;var v=m[f];if(!v)return u=!1,s;var g=t.resultAction.Ignore;switch(n){case t.requestAction.Add:g=p(v);break;case t.requestAction.Delete:g=h(v)}s.push(c(v,g,d,f)),l++}return s},e&&e.tagName&&("td"===e.tagName.toLowerCase()||"th"===e.tagName.toLowerCase())&&(r.colPos=e.cellIndex,e.parentElement&&e.parentElement.tagName&&"tr"===e.parentElement.tagName.toLowerCase()&&(r.rowPos=e.parentElement.rowIndex)),function(){for(var t=i.rows,e=0;e"),s=new Gt(o,Gt.where.Row,Gt.requestAction.Add,r()(n).closest("table")[0]).getActionList(),l=0;l"+pt.blank+"");break;case Gt.resultAction.SumSpanCount:if("top"===e&&(c.baseCell.parent?c.baseCell.closest("tr").rowIndex:0)<=n[0].rowIndex){var d=r()("
").append(r()(""+pt.blank+"").removeAttr("rowspan")).html();a.append(d);break}var f=parseInt(c.baseCell.rowSpan,10);f++,c.baseCell.setAttribute("rowSpan",f)}}if("top"===e)n.before(a);else{if(o.rowSpan>1){var h=n[0].rowIndex+(o.rowSpan-2);return void r()(r()(n).parent().find("tr")[h]).after(r()(a))}n.after(a)}}},{key:"addCol",value:function(t,e){var o=pt.ancestor(t.commonAncestor(),pt.isCell),n=r()(o).closest("tr");r()(n).siblings().push(n);for(var i=new Gt(o,Gt.where.Column,Gt.requestAction.Add,r()(n).closest("table")[0]).getActionList(),a=0;a"+pt.blank+""):r()(s.baseCell).before(""+pt.blank+"");break;case Gt.resultAction.SumSpanCount:if("right"===e){var c=parseInt(s.baseCell.colSpan,10);c++,s.baseCell.setAttribute("colSpan",c)}else r()(s.baseCell).before(""+pt.blank+"")}}}},{key:"recoverAttributes",value:function(t){var e="";if(!t)return e;for(var o=t.attributes||[],n=0;n1,d=u?parseInt(l.rowSpan,10):0;switch(a[s].action){case Gt.resultAction.Ignore:continue;case Gt.resultAction.AddCell:var f=o.next("tr")[0];if(!f)continue;var h=o[0].cells[n];u&&(d>2?(d--,f.insertBefore(h,f.cells[n]),f.cells[n].setAttribute("rowSpan",d),f.cells[n].innerHTML=""):2===d&&(f.insertBefore(h,f.cells[n]),f.cells[n].removeAttribute("rowSpan"),f.cells[n].innerHTML=""));continue;case Gt.resultAction.SubtractSpanCount:u&&(d>2?(d--,l.setAttribute("rowSpan",d),c.rowIndex!==i&&l.cellIndex===n&&(l.innerHTML="")):2===d&&(l.removeAttribute("rowSpan"),c.rowIndex!==i&&l.cellIndex===n&&(l.innerHTML="")));continue;case Gt.resultAction.RemoveCell:continue}}o.remove()}},{key:"deleteCol",value:function(t){for(var e=pt.ancestor(t.commonAncestor(),pt.isCell),o=r()(e).closest("tr"),n=o.children("td, th").index(r()(e)),i=new Gt(e,Gt.where.Column,Gt.requestAction.Delete,r()(o).closest("table")[0]).getActionList(),a=0;a1){var l=s.colSpan?parseInt(s.colSpan,10):0;l>2?(l--,s.setAttribute("colSpan",l),s.cellIndex===n&&(s.innerHTML="")):2===l&&(s.removeAttribute("colSpan"),s.cellIndex===n&&(s.innerHTML=""))}continue;case Gt.resultAction.RemoveCell:pt.remove(i[a].baseCell,!0);continue}}},{key:"createTable",value:function(t,e,o){for(var n,i=[],a=0;a"+pt.blank+"");n=i.join("");for(var s,l=[],c=0;c"+n+"");s=l.join("");var u=r()(""+s+"
");return o&&o.tableClassName&&u.addClass(o.tableClassName),u[0]}},{key:"deleteTable",value:function(t){var e=pt.ancestor(t.commonAncestor(),pt.isCell);r()(e).closest("table").remove()}}],e&&Vt(t.prototype,e),o&&Vt(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function Yt(t){return Yt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Yt(t)}function Xt(t,e){for(var o=0;o0&&o.isLimited(u))){var d=c.toString()!==i;"string"==typeof n&&(n=n.trim()),n=o.options.onCreateLink?o.options.onCreateLink(n):o.checkLinkUrl(n);var f=[];if(d){var h=(c=c.deleteContents()).insertNode(r()("").text(i)[0]);f.push(h)}else f=o.style.styleNodes(c,{nodeName:"A",expandClosestSibling:!0,onlyPartialContains:!0});r().each(f,(function(t,o){r()(o).attr("href",n),a?(r()(o).attr("target","_blank"),s&&e.push("noreferrer"),l&&e.push("noopener"),e.length&&r()(o).attr("rel",e.join(" "))):r()(o).removeAttr("target")})),o.setLastRange(o.createRangeFromList(f).select())}})),this.color=this.wrapCommand((function(t){var e=t.foreColor,o=t.backColor;e&&document.execCommand("foreColor",!1,e),o&&document.execCommand("backColor",!1,o)})),this.foreColor=this.wrapCommand((function(t){document.execCommand("foreColor",!1,t)})),this.insertTable=this.wrapCommand((function(t){var e=t.split("x");o.getLastRange().deleteContents().insertNode(o.table.createTable(e[0],e[1],o.options))})),this.removeMedia=this.wrapCommand((function(){var t=r()(o.restoreTarget()).parent();t.closest("figure").length?t.closest("figure").remove():t=r()(o.restoreTarget()).detach(),o.setLastRange(Tt.createFromSelection(t).select()),o.context.triggerEvent("media.delete",t,o.$editable)})),this.floatMe=this.wrapCommand((function(t){var e=r()(o.restoreTarget());e.toggleClass("note-float-left","left"===t),e.toggleClass("note-float-right","right"===t),e.css("float","none"===t?"":t)})),this.resize=this.wrapCommand((function(t){var e=r()(o.restoreTarget());0===(t=parseFloat(t))?e.css("width",""):e.css({width:100*t+"%",height:""})}))},e=[{key:"initialize",value:function(){var t=this;this.$editable.on("keydown",(function(e){if(e.keyCode===Nt.code.ENTER&&t.context.triggerEvent("enter",e),t.context.triggerEvent("keydown",e),t.snapshot=t.history.makeSnapshot(),t.hasKeyShortCut=!1,e.isDefaultPrevented()||(t.options.shortcuts?t.hasKeyShortCut=t.handleKeyMap(e):t.preventDefaultEditableShortCuts(e)),t.isLimited(1,e)){var o=t.getLastRange();if(o.eo-o.so==0)return!1}t.setLastRange(),t.options.recordEveryKeystroke&&!1===t.hasKeyShortCut&&t.history.recordUndo()})).on("keyup",(function(e){t.setLastRange(),t.context.triggerEvent("keyup",e)})).on("focus",(function(e){t.setLastRange(),t.context.triggerEvent("focus",e)})).on("blur",(function(e){t.context.triggerEvent("blur",e)})).on("mousedown",(function(e){t.context.triggerEvent("mousedown",e)})).on("mouseup",(function(e){t.setLastRange(),t.history.recordUndo(),t.context.triggerEvent("mouseup",e)})).on("scroll",(function(e){t.context.triggerEvent("scroll",e)})).on("paste",(function(e){t.setLastRange(),t.context.triggerEvent("paste",e)})).on("copy",(function(e){t.context.triggerEvent("copy",e)})).on("input",(function(){t.isLimited(0)&&t.snapshot&&t.history.applySnapshot(t.snapshot)})),this.$editable.attr("spellcheck",this.options.spellCheck),this.$editable.attr("autocorrect",this.options.spellCheck),this.options.disableGrammar&&this.$editable.attr("data-gramm",!1),this.$editable.html(pt.html(this.$note)||pt.emptyPara),this.$editable.on(m.inputEventName,g.debounce((function(){t.context.triggerEvent("change",t.$editable.html(),t.$editable)}),10)),this.$editable.on("focusin",(function(e){t.context.triggerEvent("focusin",e)})).on("focusout",(function(e){t.context.triggerEvent("focusout",e)})),this.options.airMode?this.options.overrideContextMenu&&this.$editor.on("contextmenu",(function(e){return t.context.triggerEvent("contextmenu",e),!1})):(this.options.width&&this.$editor.outerWidth(this.options.width),this.options.height&&this.$editable.outerHeight(this.options.height),this.options.maxHeight&&this.$editable.css("max-height",this.options.maxHeight),this.options.minHeight&&this.$editable.css("min-height",this.options.minHeight)),this.history.recordUndo(),this.setLastRange()}},{key:"destroy",value:function(){this.$editable.off()}},{key:"handleKeyMap",value:function(t){var e=this.options.keyMap[m.isMac?"mac":"pc"],o=[];t.metaKey&&o.push("CMD"),t.ctrlKey&&!t.altKey&&o.push("CTRL"),t.shiftKey&&o.push("SHIFT");var n=Nt.nameFromCode[t.keyCode];n&&o.push(n);var i=e[o.join("+")];if("TAB"!==n||this.options.tabDisable)if(i){if(!1!==this.context.invoke(i))return t.preventDefault(),!0}else Nt.isEdit(t.keyCode)&&(Nt.isRemove(t.keyCode)&&this.context.invoke("removed"),this.afterCommand());else this.afterCommand();return!1}},{key:"preventDefaultEditableShortCuts",value:function(t){(t.ctrlKey||t.metaKey)&&C.contains([66,73,85],t.keyCode)&&t.preventDefault()}},{key:"isLimited",value:function(t,e){return t=t||0,(void 0===e||!(Nt.isMove(e.keyCode)||Nt.isNavigation(e.keyCode)||e.ctrlKey||e.metaKey||C.contains([Nt.code.BACKSPACE,Nt.code.DELETE],e.keyCode)))&&this.options.maxTextLength>0&&this.$editable.text().length+t>this.options.maxTextLength}},{key:"checkLinkUrl",value:function(t){return Jt.test(t)?"mailto://"+t:te.test(t)?"tel://"+t:ee.test(t)?t:"http://"+t}},{key:"createRange",value:function(){return this.focus(),this.setLastRange(),this.getLastRange()}},{key:"createRangeFromList",value:function(t){var e=Tt.createFromNodeBefore(C.head(t)).getStartPoint(),o=Tt.createFromNodeAfter(C.last(t)).getEndPoint();return Tt.create(e.node,e.offset,o.node,o.offset)}},{key:"setLastRange",value:function(t){t?this.lastRange=t:(this.lastRange=Tt.create(this.editable),0===r()(this.lastRange.sc).closest(".note-editable").length&&(this.lastRange=Tt.createFromBodyElement(this.editable)))}},{key:"getLastRange",value:function(){return this.lastRange||this.setLastRange(),this.lastRange}},{key:"saveRange",value:function(t){t&&this.getLastRange().collapse().select()}},{key:"restoreRange",value:function(){this.lastRange&&(this.lastRange.select(),this.focus())}},{key:"saveTarget",value:function(t){this.$editable.data("target",t)}},{key:"clearTarget",value:function(){this.$editable.removeData("target")}},{key:"restoreTarget",value:function(){return this.$editable.data("target")}},{key:"currentStyle",value:function(){var t=Tt.create();return t&&(t=t.normalize()),t?this.style.current(t):this.style.fromNode(this.$editable)}},{key:"styleFromNode",value:function(t){return this.style.fromNode(t)}},{key:"undo",value:function(){this.context.triggerEvent("before.command",this.$editable.html()),this.history.undo(),this.context.triggerEvent("change",this.$editable.html(),this.$editable)}},{key:"commit",value:function(){this.context.triggerEvent("before.command",this.$editable.html()),this.history.commit(),this.context.triggerEvent("change",this.$editable.html(),this.$editable)}},{key:"redo",value:function(){this.context.triggerEvent("before.command",this.$editable.html()),this.history.redo(),this.context.triggerEvent("change",this.$editable.html(),this.$editable)}},{key:"beforeCommand",value:function(){this.context.triggerEvent("before.command",this.$editable.html()),document.execCommand("styleWithCSS",!1,this.options.styleWithCSS),this.focus()}},{key:"afterCommand",value:function(t){this.normalizeContent(),this.history.recordUndo(),t||this.context.triggerEvent("change",this.$editable.html(),this.$editable)}},{key:"tab",value:function(){var t=this.getLastRange();if(t.isCollapsed()&&t.isOnCell())this.table.tab(t);else{if(0===this.options.tabSize)return!1;this.isLimited(this.options.tabSize)||(this.beforeCommand(),this.typing.insertTab(t,this.options.tabSize),this.afterCommand())}}},{key:"untab",value:function(){var t=this.getLastRange();if(t.isCollapsed()&&t.isOnCell())this.table.tab(t,!0);else if(0===this.options.tabSize)return!1}},{key:"wrapCommand",value:function(t){return function(){this.beforeCommand(),t.apply(this,arguments),this.afterCommand()}}},{key:"removed",value:function(t,e,o){(t=Tt.create()).isCollapsed()&&t.isOnCell()&&(o=(e=t.ec).tagName)&&1===e.childElementCount&&"BR"===e.childNodes[0].tagName&&("P"===o?e.remove():["TH","TD"].indexOf(o)>=0&&e.firstChild.remove())}},{key:"insertImage",value:function(t,e){var o,n=this;return(o=t,r().Deferred((function(t){var e=r()("");e.one("load",(function(){e.off("error abort"),t.resolve(e)})).one("error abort",(function(){e.off("load").detach(),t.reject(e)})).css({display:"none"}).appendTo(document.body).attr("src",o)})).promise()).then((function(t){n.beforeCommand(),"function"==typeof e?e(t):("string"==typeof e&&t.attr("data-filename",e),t.css("width",Math.min(n.$editable.width(),t.width()))),t.show(),n.getLastRange().insertNode(t[0]),n.setLastRange(Tt.createFromNodeAfter(t[0]).select()),n.afterCommand()})).fail((function(t){n.context.triggerEvent("image.upload.error",t)}))}},{key:"insertImagesAsDataURL",value:function(t){var e=this;r().each(t,(function(t,o){var n=o.name;e.options.maximumImageFileSize&&e.options.maximumImageFileSize":t),e&&e.length&&(e[0].tagName.toUpperCase()!==t.toUpperCase()&&(e=e.find(t)),e&&e.length)){var o=this.createRange(),n=r()([o.sc,o.ec]).closest(t);n.removeClass();var i=e[0].className||"";i&&n.addClass(i)}}},{key:"formatPara",value:function(){this.formatBlock("P")}},{key:"fontStyling",value:function(t,e){var o=this.getLastRange();if(""!==o){var n=this.style.styleNodes(o);if(this.$editor.find(".note-status-output").html(""),r()(n).css(t,e),o.isCollapsed()){var i=C.head(n);i&&!pt.nodeLength(i)&&(i.innerHTML=pt.ZERO_WIDTH_NBSP_CHAR,Tt.createFromNode(i.firstChild).select(),this.setLastRange(),this.$editable.data("bogus",i))}else o.select()}else{var a=r().now();this.$editor.find(".note-status-output").html('
'+this.lang.output.noSelection+"
"),setTimeout((function(){r()("#note-status-output-"+a).remove()}),5e3)}}},{key:"unlink",value:function(){var t=this.getLastRange();if(t.isOnAnchor()){var e=pt.ancestor(t.sc,pt.isAnchor);(t=Tt.createFromNode(e)).select(),this.setLastRange(),this.beforeCommand(),document.execCommand("unlink"),this.afterCommand()}}},{key:"getLinkInfo",value:function(){this.hasFocus()||this.focus();var t=this.getLastRange().expand(pt.isAnchor),e=r()(C.head(t.nodes(pt.isAnchor))),o={range:t,text:t.toString(),url:e.length?e.attr("href"):""};return e.length&&(o.isNewWindow="_blank"===e.attr("target")),o}},{key:"addRow",value:function(t){var e=this.getLastRange(this.$editable);e.isCollapsed()&&e.isOnCell()&&(this.beforeCommand(),this.table.addRow(e,t),this.afterCommand())}},{key:"addCol",value:function(t){var e=this.getLastRange(this.$editable);e.isCollapsed()&&e.isOnCell()&&(this.beforeCommand(),this.table.addCol(e,t),this.afterCommand())}},{key:"deleteRow",value:function(){var t=this.getLastRange(this.$editable);t.isCollapsed()&&t.isOnCell()&&(this.beforeCommand(),this.table.deleteRow(t),this.afterCommand())}},{key:"deleteCol",value:function(){var t=this.getLastRange(this.$editable);t.isCollapsed()&&t.isOnCell()&&(this.beforeCommand(),this.table.deleteCol(t),this.afterCommand())}},{key:"deleteTable",value:function(){var t=this.getLastRange(this.$editable);t.isCollapsed()&&t.isOnCell()&&(this.beforeCommand(),this.table.deleteTable(t),this.afterCommand())}},{key:"resizeTo",value:function(t,e,o){var n;if(o){var i=t.y/t.x,r=e.data("ratio");n={width:r>i?t.x:t.y/r,height:r>i?t.x*r:t.y}}else n={width:t.x,height:t.y};e.css(n)}},{key:"hasFocus",value:function(){return this.$editable.is(":focus")}},{key:"focus",value:function(){this.hasFocus()||this.$editable.trigger("focus")}},{key:"isEmpty",value:function(){return pt.isEmpty(this.$editable[0])||pt.emptyPara===this.$editable.html()}},{key:"empty",value:function(){this.context.invoke("code",pt.emptyPara)}},{key:"normalizeContent",value:function(){this.$editable[0].normalize()}}],e&&Xt(t.prototype,e),o&&Xt(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function ne(t){return ne="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},ne(t)}function ie(t,e){for(var o=0;o0&&this.options.allowClipboardImagePasting&&(this.context.invoke("editor.insertImagesOrCallback",n),t.preventDefault()),i.length>0&&this.context.invoke("editor.isLimited",i.length)&&t.preventDefault()}else if(window.clipboardData){var r=window.clipboardData.getData("text");this.context.invoke("editor.isLimited",r.length)&&t.preventDefault()}setTimeout((function(){e.context.invoke("editor.afterCommand")}),10)}}}])&&ie(t.prototype,e),o&&ie(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function se(t){return se="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},se(t)}function le(t,e){for(var o=0;o','
',""].join("")).prependTo(this.$editor)},e=[{key:"initialize",value:function(){this.options.disableDragAndDrop?(this.documentEventHandlers.onDrop=function(t){t.preventDefault()},this.$eventListener=this.$dropzone,this.$eventListener.on("drop",this.documentEventHandlers.onDrop)):this.attachDragAndDropEvent()}},{key:"attachDragAndDropEvent",value:function(){var t=this,e=r()(),o=this.$dropzone.find(".note-dropzone-message");this.documentEventHandlers.onDragenter=function(n){var i=t.context.invoke("codeview.isActivated"),r=t.$editor.width()>0&&t.$editor.height()>0;i||e.length||!r||(t.$editor.addClass("dragover"),t.$dropzone.width(t.$editor.width()),t.$dropzone.height(t.$editor.height()),o.text(t.lang.image.dragImageHere)),e=e.add(n.target)},this.documentEventHandlers.onDragleave=function(o){(e=e.not(o.target)).length&&"BODY"!==o.target.nodeName||(e=r()(),t.$editor.removeClass("dragover"))},this.documentEventHandlers.onDrop=function(){e=r()(),t.$editor.removeClass("dragover")},this.$eventListener.on("dragenter",this.documentEventHandlers.onDragenter).on("dragleave",this.documentEventHandlers.onDragleave).on("drop",this.documentEventHandlers.onDrop),this.$dropzone.on("dragenter",(function(){t.$dropzone.addClass("hover"),o.text(t.lang.image.dropImage)})).on("dragleave",(function(){t.$dropzone.removeClass("hover"),o.text(t.lang.image.dragImageHere)})),this.$dropzone.on("drop",(function(e){var o=e.originalEvent.dataTransfer;e.preventDefault(),o&&o.files&&o.files.length?(t.$editable.trigger("focus"),t.context.invoke("editor.insertImagesOrCallback",o.files)):r().each(o.types,(function(e,n){if(!(n.toLowerCase().indexOf("_moz_")>-1)){var i=o.getData(n);n.toLowerCase().indexOf("text")>-1?t.context.invoke("editor.pasteHTML",i):r()(i).each((function(e,o){t.context.invoke("editor.insertNode",o)}))}}))})).on("dragover",!1)}},{key:"destroy",value:function(){var t=this;Object.keys(this.documentEventHandlers).forEach((function(e){t.$eventListener.off(e.slice(2).toLowerCase(),t.documentEventHandlers[e])})),this.documentEventHandlers={}}}],e&&le(t.prototype,e),o&&le(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function de(t){return de="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},de(t)}function fe(t,e){var o="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!o){if(Array.isArray(t)||(o=function(t,e){if(t){if("string"==typeof t)return he(t,e);var o={}.toString.call(t).slice(8,-1);return"Object"===o&&t.constructor&&(o=t.constructor.name),"Map"===o||"Set"===o?Array.from(t):"Arguments"===o||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(o)?he(t,e):void 0}}(t))||e&&t&&"number"==typeof t.length){o&&(t=o);var n=0,i=function(){};return{s:i,n:function(){return n>=t.length?{done:!0}:{done:!1,value:t[n++]}},e:function(t){throw t},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var r,a=!0,s=!1;return{s:function(){o=o.call(t)},n:function(){var t=o.next();return a=t.done,t},e:function(t){s=!0,r=t},f:function(){try{a||null==o.return||o.return()}finally{if(s)throw r}}}}function he(t,e){(null==e||e>t.length)&&(e=t.length);for(var o=0,n=Array(e);o.*?(?:<\/iframe>)?)/gi,(function(t){if(/<.+src(?==?('|"|\s)?)[\s\S]+src(?=('|"|\s)?)[^>]*?>/i.test(t))return"";var o,n=fe(e);try{for(n.s();!(o=n.n()).done;){var i=o.value;if(new RegExp('src="(https?:)?//'+i.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")+'/(.+)"').test(t))return t}}catch(t){n.e(t)}finally{n.f()}return""}))}return t}},{key:"activate",value:function(){var t=this,e=this.CodeMirrorConstructor;if(this.$codable.val(pt.html(this.$editable,this.options.prettifyHtml)),this.$codable.height(this.$editable.height()),this.context.invoke("toolbar.updateCodeview",!0),this.context.invoke("airPopover.updateCodeview",!0),this.$editor.addClass("codeview"),this.$codable.trigger("focus"),e){var o=e.fromTextArea(this.$codable[0],this.options.codemirror);if(this.options.codemirror.tern){var n=new e.TernServer(this.options.codemirror.tern);o.ternServer=n,o.on("cursorActivity",(function(t){n.updateArgHints(t)}))}o.on("blur",(function(e){t.context.triggerEvent("blur.codeview",o.getValue(),e)})),o.on("change",(function(){t.context.triggerEvent("change.codeview",o.getValue(),o)})),o.setSize(null,this.$editable.outerHeight()),this.$codable.data("cmEditor",o)}else this.$codable.on("blur",(function(e){t.context.triggerEvent("blur.codeview",t.$codable.val(),e)})),this.$codable.on("input",(function(){t.context.triggerEvent("change.codeview",t.$codable.val(),t.$codable)}))}},{key:"deactivate",value:function(){if(this.CodeMirrorConstructor){var t=this.$codable.data("cmEditor");this.$codable.val(t.getValue()),t.toTextArea()}var e=this.purify(pt.value(this.$codable,this.options.prettifyHtml)||pt.emptyPara),o=this.$editable.html()!==e;this.$editable.html(e),this.$editable.height(this.options.height?this.$codable.height():"auto"),this.$editor.removeClass("codeview"),o&&this.context.triggerEvent("change",this.$editable.html(),this.$editable),this.$editable.trigger("focus"),this.context.invoke("toolbar.updateCodeview",!1),this.context.invoke("airPopover.updateCodeview",!1)}},{key:"destroy",value:function(){this.isActivated()&&this.deactivate()}}],e&&pe(t.prototype,e),o&&pe(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function ge(t){return ge="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},ge(t)}function be(t,e){for(var o=0;o0?Math.max(r,t.options.minheight):r,r=t.options.maxHeight>0?Math.min(r,t.options.maxHeight):r,a=t.options.minheight>0?Math.max(a,t.options.minheight):a,a=t.options.maxHeight>0?Math.min(a,t.options.maxHeight):a,t.$editable.height(r),t.$codable.height(a)};t.$document.on("mousemove touchmove",i).one("mouseup touchend",(function(){t.$document.off("mousemove touchmove",i)}))}))}},{key:"destroy",value:function(){this.$statusbar.off(),this.$statusbar.addClass("locked")}}])&&be(t.prototype,e),o&&be(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function we(t){return we="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},we(t)}function Ce(t,e){for(var o=0;o','
','
','
','
','
','
',this.options.disableResizeImage?"":'
',"
",""].join("")).prependTo(this.$editingArea),this.$handle.on("mousedown",(function(e){if(pt.isControlSizing(e.target)){e.preventDefault(),e.stopPropagation();var o=t.$handle.find(".note-control-selection").data("target"),n=o.offset(),i=t.$document.scrollTop(),r=function(e){t.context.invoke("editor.resizeTo",{x:e.clientX-n.left,y:e.clientY-(n.top-i)},o,!e.shiftKey),t.update(o[0],e)};t.$document.on("mousemove",r).one("mouseup",(function(e){e.preventDefault(),t.$document.off("mousemove",r),t.context.invoke("editor.afterCommand")})),o.data("ratio")||o.data("ratio",o.height()/o.width())}})),this.$handle.on("wheel",(function(e){e.preventDefault(),t.update()}))}},{key:"destroy",value:function(){this.$handle.remove()}},{key:"update",value:function(t,e){if(this.context.isDisabled())return!1;var o=pt.isImg(t),n=this.$handle.find(".note-control-selection");if(this.context.invoke("imagePopover.update",t,e),o){var i=r()(t),a=this.$editingArea[0].getBoundingClientRect(),s=t.getBoundingClientRect();n.css({display:"block",left:s.left-a.left,top:s.top-a.top,width:s.width,height:s.height}).data("target",i);var l=new Image;l.src=i.attr("src");var c=s.width+"x"+s.height+" ("+this.lang.image.original+": "+l.width+"x"+l.height+")";n.find(".note-control-selection-info").text(c),this.context.invoke("editor.saveTarget",t)}else this.hide();return o}},{key:"hide",value:function(){this.context.invoke("editor.clearTarget"),this.$handle.children().hide()}}],e&&Te(t.prototype,e),o&&Te(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function Ie(t){return Ie="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Ie(t)}function $e(t,e){for(var o=0;o").html(n).attr("href",o)[0];this.context.options.linkTargetBlank&&r()(i).attr("target","_blank"),this.lastWordRange.insertNode(i),this.lastWordRange=null,this.context.invoke("editor.focus"),this.context.triggerEvent("change",this.$editable.html(),this.$editable)}}}},{key:"handleKeydown",value:function(t){if(C.contains([Nt.code.ENTER,Nt.code.SPACE],t.keyCode)){var e=this.context.invoke("editor.createRange").getWordRange();this.lastWordRange=e}}},{key:"handleKeyup",value:function(t){(Nt.code.SPACE===t.keyCode||Nt.code.ENTER===t.keyCode&&!t.shiftKey)&&this.replace()}}])&&$e(t.prototype,e),o&&$e(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function Fe(t){return Fe="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Fe(t)}function De(t,e){for(var o=0;o'),this.$placeholder.on("click",(function(){t.context.invoke("focus")})).html(this.options.placeholder).prependTo(this.$editingArea),this.update()}},{key:"destroy",value:function(){this.$placeholder.remove()}},{key:"update",value:function(){var t=!this.context.invoke("codeview.isActivated")&&this.context.invoke("editor.isEmpty");this.$placeholder.toggle(t)}}])&&We(t.prototype,e),o&&We(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function Ve(t){return Ve="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Ve(t)}function _e(t,e){for(var o=0;o','
'+this.lang.color.background+"
","
",'","
",'
\x3c!-- back colors --\x3e
',"
",'",'',"
",'
',""].join(""):"")+(n?['
','
'+this.lang.color.foreground+"
","
",'","
",'
\x3c!-- fore colors --\x3e
',"
",'",'',"
",'
',"
"].join(""):""),callback:function(t){t.find(".note-holder").each((function(t,e){var o=r()(e);o.append(i.ui.palette({colors:i.options.colors,colorsName:i.options.colorsName,eventName:o.data("event"),container:i.options.container,tooltip:i.options.tooltip}).render())}));var e=[["#FFFFFF","#FFFFFF","#FFFFFF","#FFFFFF","#FFFFFF","#FFFFFF","#FFFFFF","#FFFFFF"]];t.find(".note-holder-custom").each((function(t,o){var n=r()(o);n.append(i.ui.palette({colors:e,colorsName:e,eventName:n.data("event"),container:i.options.container,tooltip:i.options.tooltip}).render())})),t.find("input[type=color]").each((function(e,o){r()(o).on("change",(function(){var e=t.find("#"+r()(this).data("event")).find(".note-color-btn").first(),o=this.value.toUpperCase();e.css("background-color",o).attr("aria-label",o).attr("data-value",o).attr("data-original-title",o),e.trigger("click")}))}))},click:function(e){e.stopPropagation();var o=r()("."+t).find(".note-dropdown-menu"),n=r()(e.target),a=n.data("event"),s=n.attr("data-value");if("openPalette"===a){var l=o.find("#"+s),c=r()(o.find("#"+l.data("event")).find(".note-color-row")[0]),u=c.find(".note-color-btn").last().detach(),d=l.val();u.css("background-color",d).attr("aria-label",d).attr("data-value",d).attr("data-original-title",d),c.prepend(u),l.trigger("click")}else{if(C.contains(["backColor","foreColor"],a)){var f="backColor"===a?"background-color":"color",h=n.closest(".note-color").find(".note-recent-color"),p=n.closest(".note-color").find(".note-current-color-button");h.css(f,s),p.attr("data-"+a,s)}i.context.invoke("editor."+a,s)}}})]}).render()}},{key:"addToolbarButtons",value:function(){var t=this;this.context.memo("button.style",(function(){return t.ui.buttonGroup([t.button({className:"dropdown-toggle",contents:t.ui.dropdownButtonContents(t.ui.icon(t.options.icons.magic),t.options),tooltip:t.lang.style.style,data:{toggle:"dropdown"}}),t.ui.dropdown({className:"dropdown-style",items:t.options.styleTags,title:t.lang.style.style,template:function(e){"string"==typeof e&&(e={tag:e,title:Object.prototype.hasOwnProperty.call(t.lang.style,e)?t.lang.style[e]:e});var o=e.tag,n=e.title;return"<"+o+(e.style?' style="'+e.style+'" ':"")+(e.className?' class="'+e.className+'"':"")+">"+n+""},click:t.context.createInvokeHandler("editor.formatBlock")})]).render()}));for(var e=function(){var e=t.options.styleTags[o];t.context.memo("button.style."+e,(function(){return t.button({className:"note-btn-style-"+e,contents:'
'+e.toUpperCase()+"
",tooltip:t.lang.style[e],click:t.context.createInvokeHandler("editor.formatBlock")}).render()}))},o=0,n=this.options.styleTags.length;o',t.options),tooltip:t.lang.font.name,data:{toggle:"dropdown"}}),t.ui.dropdownCheck({className:"dropdown-fontname",checkClassName:t.options.icons.menuCheck,items:t.options.fontNames.filter(t.isFontInstalled.bind(t)),title:t.lang.font.name,template:function(t){return''+t+""},click:t.context.createInvokeHandlerAndUpdateState("editor.fontName")})]).render()})),this.context.memo("button.fontsize",(function(){return t.ui.buttonGroup([t.button({className:"dropdown-toggle",contents:t.ui.dropdownButtonContents('',t.options),tooltip:t.lang.font.size,data:{toggle:"dropdown"}}),t.ui.dropdownCheck({className:"dropdown-fontsize",checkClassName:t.options.icons.menuCheck,items:t.options.fontSizes,title:t.lang.font.size,click:t.context.createInvokeHandlerAndUpdateState("editor.fontSize")})]).render()})),this.context.memo("button.fontsizeunit",(function(){return t.ui.buttonGroup([t.button({className:"dropdown-toggle",contents:t.ui.dropdownButtonContents('',t.options),tooltip:t.lang.font.sizeunit,data:{toggle:"dropdown"}}),t.ui.dropdownCheck({className:"dropdown-fontsizeunit",checkClassName:t.options.icons.menuCheck,items:t.options.fontSizeUnits,title:t.lang.font.sizeunit,click:t.context.createInvokeHandlerAndUpdateState("editor.fontSizeUnit")})]).render()})),this.context.memo("button.color",(function(){return t.colorPalette("note-color-all",t.lang.color.recent,!0,!0)})),this.context.memo("button.forecolor",(function(){return t.colorPalette("note-color-fore",t.lang.color.foreground,!1,!0)})),this.context.memo("button.backcolor",(function(){return t.colorPalette("note-color-back",t.lang.color.background,!0,!1)})),this.context.memo("button.ul",(function(){return t.button({contents:t.ui.icon(t.options.icons.unorderedlist),tooltip:t.lang.lists.unordered+t.representShortcut("insertUnorderedList"),click:t.context.createInvokeHandler("editor.insertUnorderedList")}).render()})),this.context.memo("button.ol",(function(){return t.button({contents:t.ui.icon(t.options.icons.orderedlist),tooltip:t.lang.lists.ordered+t.representShortcut("insertOrderedList"),click:t.context.createInvokeHandler("editor.insertOrderedList")}).render()}));var i=this.button({contents:this.ui.icon(this.options.icons.alignLeft),tooltip:this.lang.paragraph.left+this.representShortcut("justifyLeft"),click:this.context.createInvokeHandler("editor.justifyLeft")}),a=this.button({contents:this.ui.icon(this.options.icons.alignCenter),tooltip:this.lang.paragraph.center+this.representShortcut("justifyCenter"),click:this.context.createInvokeHandler("editor.justifyCenter")}),s=this.button({contents:this.ui.icon(this.options.icons.alignRight),tooltip:this.lang.paragraph.right+this.representShortcut("justifyRight"),click:this.context.createInvokeHandler("editor.justifyRight")}),l=this.button({contents:this.ui.icon(this.options.icons.alignJustify),tooltip:this.lang.paragraph.justify+this.representShortcut("justifyFull"),click:this.context.createInvokeHandler("editor.justifyFull")}),c=this.button({contents:this.ui.icon(this.options.icons.outdent),tooltip:this.lang.paragraph.outdent+this.representShortcut("outdent"),click:this.context.createInvokeHandler("editor.outdent")}),u=this.button({contents:this.ui.icon(this.options.icons.indent),tooltip:this.lang.paragraph.indent+this.representShortcut("indent"),click:this.context.createInvokeHandler("editor.indent")});this.context.memo("button.justifyLeft",g.invoke(i,"render")),this.context.memo("button.justifyCenter",g.invoke(a,"render")),this.context.memo("button.justifyRight",g.invoke(s,"render")),this.context.memo("button.justifyFull",g.invoke(l,"render")),this.context.memo("button.outdent",g.invoke(c,"render")),this.context.memo("button.indent",g.invoke(u,"render")),this.context.memo("button.paragraph",(function(){return t.ui.buttonGroup([t.button({className:"dropdown-toggle",contents:t.ui.dropdownButtonContents(t.ui.icon(t.options.icons.alignLeft),t.options),tooltip:t.lang.paragraph.paragraph,data:{toggle:"dropdown"}}),t.ui.dropdown([t.ui.buttonGroup({className:"note-align",children:[i,a,s,l]}),t.ui.buttonGroup({className:"note-list",children:[c,u]})])]).render()})),this.context.memo("button.height",(function(){return t.ui.buttonGroup([t.button({className:"dropdown-toggle",contents:t.ui.dropdownButtonContents(t.ui.icon(t.options.icons.textHeight),t.options),tooltip:t.lang.font.height,data:{toggle:"dropdown"}}),t.ui.dropdownCheck({items:t.options.lineHeights,checkClassName:t.options.icons.menuCheck,className:"dropdown-line-height",title:t.lang.font.height,click:t.context.createInvokeHandler("editor.lineHeight")})]).render()})),this.context.memo("button.table",(function(){return t.ui.buttonGroup([t.button({className:"dropdown-toggle",contents:t.ui.dropdownButtonContents(t.ui.icon(t.options.icons.table),t.options),tooltip:t.lang.table.table,data:{toggle:"dropdown"}}),t.ui.dropdown({title:t.lang.table.table,className:"note-table",items:['
','
','
','
',"
",'
1 x 1
'].join("")})],{callback:function(e){e.find(".note-dimension-picker-mousecatcher").css({width:t.options.insertTableMaxSize.col+"em",height:t.options.insertTableMaxSize.row+"em"}).on("mousedown",t.context.createInvokeHandler("editor.insertTable")).on("mousemove",t.tableMoveHandler.bind(t))}}).render()})),this.context.memo("button.link",(function(){return t.button({contents:t.ui.icon(t.options.icons.link),tooltip:t.lang.link.link+t.representShortcut("linkDialog.show"),click:t.context.createInvokeHandler("linkDialog.show")}).render()})),this.context.memo("button.picture",(function(){return t.button({contents:t.ui.icon(t.options.icons.picture),tooltip:t.lang.image.image,click:t.context.createInvokeHandler("imageDialog.show")}).render()})),this.context.memo("button.video",(function(){return t.button({contents:t.ui.icon(t.options.icons.video),tooltip:t.lang.video.video,click:t.context.createInvokeHandler("videoDialog.show")}).render()})),this.context.memo("button.hr",(function(){return t.button({contents:t.ui.icon(t.options.icons.minus),tooltip:t.lang.hr.insert+t.representShortcut("insertHorizontalRule"),click:t.context.createInvokeHandler("editor.insertHorizontalRule")}).render()})),this.context.memo("button.fullscreen",(function(){return t.button({className:"btn-fullscreen note-codeview-keep",contents:t.ui.icon(t.options.icons.arrowsAlt),tooltip:t.lang.options.fullscreen,click:t.context.createInvokeHandler("fullscreen.toggle")}).render()})),this.context.memo("button.codeview",(function(){return t.button({className:"btn-codeview note-codeview-keep",contents:t.ui.icon(t.options.icons.code),tooltip:t.lang.options.codeview,click:t.context.createInvokeHandler("codeview.toggle")}).render()})),this.context.memo("button.redo",(function(){return t.button({contents:t.ui.icon(t.options.icons.redo),tooltip:t.lang.history.redo+t.representShortcut("redo"),click:t.context.createInvokeHandler("editor.redo")}).render()})),this.context.memo("button.undo",(function(){return t.button({contents:t.ui.icon(t.options.icons.undo),tooltip:t.lang.history.undo+t.representShortcut("undo"),click:t.context.createInvokeHandler("editor.undo")}).render()})),this.context.memo("button.help",(function(){return t.button({contents:t.ui.icon(t.options.icons.question),tooltip:t.lang.options.help,click:t.context.createInvokeHandler("helpDialog.show")}).render()}))}},{key:"addImagePopoverButtons",value:function(){var t=this;this.context.memo("button.resizeFull",(function(){return t.button({contents:'100%',tooltip:t.lang.image.resizeFull,click:t.context.createInvokeHandler("editor.resize","1")}).render()})),this.context.memo("button.resizeHalf",(function(){return t.button({contents:'50%',tooltip:t.lang.image.resizeHalf,click:t.context.createInvokeHandler("editor.resize","0.5")}).render()})),this.context.memo("button.resizeQuarter",(function(){return t.button({contents:'25%',tooltip:t.lang.image.resizeQuarter,click:t.context.createInvokeHandler("editor.resize","0.25")}).render()})),this.context.memo("button.resizeNone",(function(){return t.button({contents:t.ui.icon(t.options.icons.rollback),tooltip:t.lang.image.resizeNone,click:t.context.createInvokeHandler("editor.resize","0")}).render()})),this.context.memo("button.floatLeft",(function(){return t.button({contents:t.ui.icon(t.options.icons.floatLeft),tooltip:t.lang.image.floatLeft,click:t.context.createInvokeHandler("editor.floatMe","left")}).render()})),this.context.memo("button.floatRight",(function(){return t.button({contents:t.ui.icon(t.options.icons.floatRight),tooltip:t.lang.image.floatRight,click:t.context.createInvokeHandler("editor.floatMe","right")}).render()})),this.context.memo("button.floatNone",(function(){return t.button({contents:t.ui.icon(t.options.icons.rollback),tooltip:t.lang.image.floatNone,click:t.context.createInvokeHandler("editor.floatMe","none")}).render()})),this.context.memo("button.removeMedia",(function(){return t.button({contents:t.ui.icon(t.options.icons.trash),tooltip:t.lang.image.remove,click:t.context.createInvokeHandler("editor.removeMedia")}).render()}))}},{key:"addLinkPopoverButtons",value:function(){var t=this;this.context.memo("button.linkDialogShow",(function(){return t.button({contents:t.ui.icon(t.options.icons.link),tooltip:t.lang.link.edit,click:t.context.createInvokeHandler("linkDialog.show")}).render()})),this.context.memo("button.unlink",(function(){return t.button({contents:t.ui.icon(t.options.icons.unlink),tooltip:t.lang.link.unlink,click:t.context.createInvokeHandler("editor.unlink")}).render()}))}},{key:"addTablePopoverButtons",value:function(){var t=this;this.context.memo("button.addRowUp",(function(){return t.button({className:"btn-md",contents:t.ui.icon(t.options.icons.rowAbove),tooltip:t.lang.table.addRowAbove,click:t.context.createInvokeHandler("editor.addRow","top")}).render()})),this.context.memo("button.addRowDown",(function(){return t.button({className:"btn-md",contents:t.ui.icon(t.options.icons.rowBelow),tooltip:t.lang.table.addRowBelow,click:t.context.createInvokeHandler("editor.addRow","bottom")}).render()})),this.context.memo("button.addColLeft",(function(){return t.button({className:"btn-md",contents:t.ui.icon(t.options.icons.colBefore),tooltip:t.lang.table.addColLeft,click:t.context.createInvokeHandler("editor.addCol","left")}).render()})),this.context.memo("button.addColRight",(function(){return t.button({className:"btn-md",contents:t.ui.icon(t.options.icons.colAfter),tooltip:t.lang.table.addColRight,click:t.context.createInvokeHandler("editor.addCol","right")}).render()})),this.context.memo("button.deleteRow",(function(){return t.button({className:"btn-md",contents:t.ui.icon(t.options.icons.rowRemove),tooltip:t.lang.table.delRow,click:t.context.createInvokeHandler("editor.deleteRow")}).render()})),this.context.memo("button.deleteCol",(function(){return t.button({className:"btn-md",contents:t.ui.icon(t.options.icons.colRemove),tooltip:t.lang.table.delCol,click:t.context.createInvokeHandler("editor.deleteCol")}).render()})),this.context.memo("button.deleteTable",(function(){return t.button({className:"btn-md",contents:t.ui.icon(t.options.icons.trash),tooltip:t.lang.table.delTable,click:t.context.createInvokeHandler("editor.deleteTable")}).render()}))}},{key:"build",value:function(t,e){for(var o=0,n=e.length;o3&&c3&&ul&&ac)&&(this.isFollowing=!1,this.$toolbar.css({position:"relative",top:0,width:"100%",zIndex:"auto"}),this.$editable.css({marginTop:""}))}},{key:"changeContainer",value:function(t){t?this.$toolbar.prependTo(this.$editor):this.options.toolbarContainer&&this.$toolbar.appendTo(this.options.toolbarContainer),this.options.followingToolbar&&this.followScroll()}},{key:"updateFullscreen",value:function(t){this.ui.toggleBtnActive(this.$toolbar.find(".btn-fullscreen"),t),this.changeContainer(t)}},{key:"updateCodeview",value:function(t){this.ui.toggleBtnActive(this.$toolbar.find(".btn-codeview"),t),t?this.deactivate():this.activate()}},{key:"activate",value:function(t){var e=this.$toolbar.find("button");t||(e=e.not(".note-codeview-keep")),this.ui.toggleBtn(e,!0)}},{key:"deactivate",value:function(t){var e=this.$toolbar.find("button");t||(e=e.not(".note-codeview-keep")),this.ui.toggleBtn(e,!1)}}])&&Xe(t.prototype,e),o&&Xe(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function to(t){return to="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},to(t)}function eo(t,e){for(var o=0;o','"),''),"",'
','"),''),"
",this.options.disableLinkTarget?"":r()("
").append(this.ui.checkbox({className:"sn-checkbox-open-in-new-window",text:this.lang.link.openInNewWindow,checked:!0}).render()).html()].join(""),o='');this.$dialog=this.ui.dialog({className:"link-dialog",title:this.lang.link.insert,fade:this.options.dialogsFade,body:e,footer:o}).render().appendTo(t)}},{key:"destroy",value:function(){this.ui.hideDialog(this.$dialog),this.$dialog.remove()}},{key:"bindEnterKey",value:function(t,e){t.on("keypress",(function(t){t.keyCode===Nt.code.ENTER&&(t.preventDefault(),e.trigger("click"))}))}},{key:"checkLinkUrl",value:function(t){return no.test(t)?"mailto://"+t:io.test(t)?"tel://"+t:ro.test(t)?t:"http://"+t}},{key:"onCheckLinkUrl",value:function(t){var e=this;t.on("blur",(function(t){t.target.value=""==t.target.value?"":e.checkLinkUrl(t.target.value)}))}},{key:"toggleLinkBtn",value:function(t,e,o){this.ui.toggleBtn(t,e.val()&&o.val())}},{key:"showLinkDialog",value:function(t){var e=this;return r().Deferred((function(o){var n=e.$dialog.find(".note-link-text"),i=e.$dialog.find(".note-link-url"),r=e.$dialog.find(".note-link-btn"),a=e.$dialog.find(".sn-checkbox-open-in-new-window input[type=checkbox]");e.ui.onDialogShown(e.$dialog,(function(){e.context.triggerEvent("dialog.shown"),!t.url&&g.isValidUrl(t.text)&&(t.url=e.checkLinkUrl(t.text)),n.on("input paste propertychange",(function(){var o=n.val(),a=document.createElement("div");a.innerText=o,o=a.innerHTML,t.text=o,e.toggleLinkBtn(r,n,i)})).val(t.text),i.on("input paste propertychange",(function(){t.text||n.val(i.val()),e.toggleLinkBtn(r,n,i)})).val(t.url),m.isSupportTouch||i.trigger("focus"),e.toggleLinkBtn(r,n,i),e.bindEnterKey(i,r),e.bindEnterKey(n,r),e.onCheckLinkUrl(i);var s=void 0!==t.isNewWindow?t.isNewWindow:e.context.options.linkTargetBlank;a.prop("checked",s),r.one("click",(function(r){r.preventDefault(),o.resolve({range:t.range,url:i.val(),text:n.val(),isNewWindow:a.is(":checked")}),e.ui.hideDialog(e.$dialog)}))})),e.ui.onDialogHidden(e.$dialog,(function(){n.off(),i.off(),r.off(),"pending"===o.state()&&o.reject()})),e.ui.showDialog(e.$dialog)})).promise()}},{key:"show",value:function(){var t=this,e=this.context.invoke("editor.getLinkInfo");this.context.invoke("editor.saveRange"),this.showLinkDialog(e).then((function(e){t.context.invoke("editor.restoreRange"),t.context.invoke("editor.createLink",e)})).fail((function(){t.context.invoke("editor.restoreRange")}))}}])&&eo(t.prototype,e),o&&eo(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function so(t){return so="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},so(t)}function lo(t,e){for(var o=0;o ')}}).render().appendTo(this.options.container);var t=this.$popover.find(".popover-content,.note-popover-content");this.context.invoke("buttons.build",t,this.options.popover.link),this.$popover.on("mousedown",(function(t){t.preventDefault()}))}},{key:"destroy",value:function(){this.$popover.remove()}},{key:"update",value:function(){if(this.context.invoke("editor.hasFocus")){var t=this.context.invoke("editor.getLastRange");if(t.isCollapsed()&&t.isOnAnchor()){var e=pt.ancestor(t.sc,pt.isAnchor),o=r()(e).attr("href");this.$popover.find("a").attr("href",o).text(o);var n=pt.posFromPlaceholder(e),i=r()(this.options.container).offset();n.top-=i.top,n.left-=i.left,this.$popover.css({display:"block",left:n.left,top:n.top})}else this.hide()}else this.hide()}},{key:"hide",value:function(){this.$popover.hide()}}])&&lo(t.prototype,e),o&&lo(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function fo(t){return fo="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},fo(t)}function ho(t,e){for(var o=0;o")}var n=this.options.dialogsInBody?this.$body:this.options.container,i=['
','",'',t,"
",'
','",'',"
"].join(""),r='');this.$dialog=this.ui.dialog({title:this.lang.image.insert,fade:this.options.dialogsFade,body:i,footer:r}).render().appendTo(n)}},{key:"destroy",value:function(){this.ui.hideDialog(this.$dialog),this.$dialog.remove()}},{key:"bindEnterKey",value:function(t,e){t.on("keypress",(function(t){t.keyCode===Nt.code.ENTER&&(t.preventDefault(),e.trigger("click"))}))}},{key:"show",value:function(){var t=this;this.context.invoke("editor.saveRange"),this.showImageDialog().then((function(e){t.ui.hideDialog(t.$dialog),t.context.invoke("editor.restoreRange"),"string"==typeof e?t.options.callbacks.onImageLinkInsert?t.context.triggerEvent("image.link.insert",e):t.context.invoke("editor.insertImage",e):t.context.invoke("editor.insertImagesOrCallback",e)})).fail((function(){t.context.invoke("editor.restoreRange")}))}},{key:"showImageDialog",value:function(){var t=this;return r().Deferred((function(e){var o=t.$dialog.find(".note-image-input"),n=t.$dialog.find(".note-image-url"),i=t.$dialog.find(".note-image-btn");t.ui.onDialogShown(t.$dialog,(function(){t.context.triggerEvent("dialog.shown"),o.replaceWith(o.clone().on("change",(function(t){e.resolve(t.target.files||t.target.value)})).val("")),n.on("input paste propertychange",(function(){t.ui.toggleBtn(i,n.val())})).val(""),m.isSupportTouch||n.trigger("focus"),i.on("click",(function(t){t.preventDefault(),e.resolve(n.val())})),t.bindEnterKey(n,i)})),t.ui.onDialogHidden(t.$dialog,(function(){o.off(),n.off(),i.off(),"pending"===e.state()&&e.reject()})),t.ui.showDialog(t.$dialog)}))}}])&&ho(t.prototype,e),o&&ho(t,o),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,o}();function vo(t){return vo="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},vo(t)}function go(t,e){for(var o=0;o','"),''),""].join(""),o='');this.$dialog=this.ui.dialog({title:this.lang.video.insert,fade:this.options.dialogsFade,body:e,footer:o}).render().appendTo(t)}},{key:"destroy",value:function(){this.ui.hideDialog(this.$dialog),this.$dialog.remove()}},{key:"bindEnterKey",value:function(t,e){t.on("keypress",(function(t){t.keyCode===Nt.code.ENTER&&(t.preventDefault(),e.trigger("click"))}))}},{key:"createVideoNode",value:function(t){var e,o=t.match(/(?:youtu\.be\/|youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=|shorts\/|live\/))([^&\n?]+)(?:.*[?&]t=([^&\n]+))?.*/),n=t.match(/(?:\.|\/\/)drive\.google\.com\/file\/d\/(.[a-zA-Z0-9_-]*)\/view/),i=t.match(/(?:www\.|\/\/)instagram\.com\/(reel|p)\/(.[a-zA-Z0-9_-]*)/),a=t.match(/\/\/vine\.co\/v\/([a-zA-Z0-9]+)/),s=t.match(/\/\/(player\.)?vimeo\.com\/([a-z]*\/)*(\d+)[?]?.*/),l=t.match(/.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/),c=t.match(/\/\/v\.youku\.com\/v_show\/id_(\w+)=*\.html/),u=t.match(/\/\/(.*)\/videos\/watch\/([^?]*)(?:\?(?:start=(\w*))?(?:&stop=(\w*))?(?:&loop=([10]))?(?:&autoplay=([10]))?(?:&muted=([10]))?)?/),d=t.match(/\/\/v\.qq\.com.*?vid=(.+)/),f=t.match(/\/\/v\.qq\.com\/x?\/?(page|cover).*?\/([^\/]+)\.html\??.*/),h=t.match(/^.+.(mp4|m4v)$/),p=t.match(/^.+.(ogg|ogv)$/),m=t.match(/^.+.(webm)$/),v=t.match(/(?:www\.|\/\/)facebook\.com\/([^\/]+)\/videos\/([0-9]+)/);if(o&&11===o[1].length){var g=o[1],b=0;if(void 0!==o[2]){var y=o[2].match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/);if(y)for(var k=[3600,60,1],w=0,C=k.length;w").attr("frameborder",0).attr("src","//www.youtube.com/embed/"+g+(b>0?"?start="+b:"")).attr("width","640").attr("height","360")}else if(n&&n[0].length)e=r()("