├── .DS_Store ├── LICENSE ├── README.md ├── docs ├── .DS_Store ├── faq.md ├── images │ ├── .DS_Store │ ├── add_my_domain.png │ ├── desktop_screen010.png │ ├── desktop_screen020.png │ ├── desktop_screen030.png │ ├── desktop_screen031.png │ ├── desktop_screen040.png │ ├── desktop_screen041.png │ ├── desktop_screen050.png │ ├── desktop_screen060.png │ ├── install_jitsi_azure_0010.png │ ├── install_jitsi_azure_0020.png │ ├── install_jitsi_dig.png │ ├── install_jitsi_dns_example.png │ ├── install_jitsi_google0020.png │ ├── install_jitsi_nslookup.png │ └── thesystem_architecture.png ├── install_conferencesystem.md ├── install_customization.md ├── install_jitsi_azure.md ├── install_jitsi_google.md └── usage.md └── src_meet_ym ├── .DS_Store ├── YM_JITSI_操作說明.pdf ├── css ├── styles.css └── waiting.gif ├── images ├── .DS_Store ├── aigia.png ├── browser_setting.mp4 ├── mail │ ├── contact_me.js │ ├── contact_me.php │ └── jqBootstrapValidation.js ├── screen00.jpg ├── screen_a01.png ├── screen_a02.png ├── screen_a03.png ├── thesystem.png ├── thesystem_architecture.png ├── welcome_page │ ├── applestore_qrcode.png │ ├── appstore.svg │ ├── googleplay.png │ └── googleplay_qrcode.png ├── ym.jpg └── ymlogo.png ├── libs ├── qrcode.min.js └── scripts.js ├── meet_ym.html └── meet_ym_browser.html /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Yu-Chun Chen (陳育群) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BuildYourOwnConferenceSystem 2 | 這個計畫目的在於提供「快速架站懶人包」,協助您快速架構自己專屬、免費、安全、易用的視訊會議系統。 3 | 4 | - [Buiild Your Own Conference System](#BuildYourOwnConferenceSystem) 5 | - [動機](#動機) 6 | - [系統操作畫面](#系統操作畫面) 7 | - [安裝需求](#安裝需求) 8 | - [系統安裝說明](#系統安裝說明) 9 | - [更多訊息](#更多訊息) 10 | 11 | --- 12 | 13 | ## 動機 14 | 2020全世界面臨武漢肺炎(新型冠狀病毒, COVID-19)威脅,使用視訊會議可以兼顧團隊工作效率、減少感染風險,可惜目前市面上部分視訊軟體有[資安隱憂](https://3c.ltn.com.tw/news/40047 "自由時報"),有的軟體設定過於複雜需要專門人員操作管理,更有些需要另外額外收取費用,反而影響參與視訊會議的使用體驗。
15 | 我們依照台灣機關學校視訊會議使用情境重新撰寫視訊系統,以[教育部](https://depart.moe.edu.tw/ED2700/News_Content.aspx?n=727087A8A1328DEE&s=868B3A6EDF9BA52D)、「台灣天才IT大臣」[唐鳳](https://3c.ltn.com.tw/news/40055)推薦使用的開源軟體[Jitsi meet](https://meet.jit.si/)為基礎,重新打造一套快速入手適用於台灣的視訊會議平台。
16 | 17 | 18 | 使用「快速架站懶人包」將有下列好處: 19 | #### 學校機關系統管理者 20 | 免費軟體安裝不易,是管理者心中痛點。我們提供本機、雲端(Google cloud 與 Azure)多種安裝方式、您僅需要照著網頁操作,簡單修改原始碼就可以替單位架設一套免費、安全隱密的會議系統,還可以依照您的需求自行延伸加上傳送開會通知、會議錄影、錄音字幕等等功能。 21 | #### 視訊會議使用者 22 | 與會者一直遲遲無法加入會議,是視訊會議最常見的痛點,我們將常見的視訊會議流程簡化為4個步驟,會議管理者僅需要照著網頁提示即可開啟會議、自動產生開會通知。視訊會議參與者不需高檔配備,僅需用手機掃條碼便可即時參加會議,讓視訊會議流程最通暢。 23 | 24 | ## 系統操作畫面 25 | 我們將以「陽明大學視訊系統」流程為範例。只要跟著「快速架站懶人包」,您即可輕鬆擁有自己專屬、免費、安全、易用的視訊會議系統。 26 | ###### 系統架構 27 | ![系統架構說明](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/thesystem_architecture.png "系統架構說明") 28 | 29 | ###### 系統首頁 30 | ![系統首頁](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/desktop_screen010.png "系統首頁") 31 | 32 | ###### 視訊會議步驟一:開啟虛擬會議室 33 | ![視訊會議步驟1](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/desktop_screen020.png "視訊會議步驟一:開啟虛擬會議室") 34 | 35 | ###### 視訊會議步驟二:管理虛擬會議室 36 | ![視訊會議步驟2](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/desktop_screen030.png "視訊會議步驟二:管理虛擬會議室") 37 | 38 | ###### 視訊會議步驟三:設定密碼、開會通知 39 | ![視訊會議步驟-設定密碼、開會通知](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/desktop_screen031.png "視訊會議步驟三:設定密碼、開會通知") 40 | 41 | ###### 簡單易懂參加會議說明 42 | ![視訊會議與會](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/desktop_screen040.png) 43 | 44 | ###### 視訊會議實際畫面 45 | ![視訊會議異地開會](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/desktop_screen041.png) 46 | 47 | ###### 系統說明 48 | ![系統說明](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/desktop_screen050.png) 49 | 50 | ###### 開發團隊 51 | ![團隊](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/desktop_screen060.png) 52 | 53 | ## 安裝需求 54 | * 您必須有台安裝好Ubuntu LTS主機(或者您可以申請Azure或Google雲端帳號,我們也有提供安裝指引可以安裝在 Azure雲端 與 Google雲端,管理更為方便), **必要** 55 | * 您的網域名稱和DNS紀錄(Valid domain with DNS record), **必要**. 56 | 57 | ## 系統安裝說明 58 | 系統安裝主要分成後端Jitsi視訊主機架設以及視訊系統前台兩大部分。 59 | 1. 安裝設定Jitsi meet系統後端 60 | 1. 使用 [Azure 雲端](./docs/install_jitsi_azure.md) 61 | 2. 使用 [Google 雲端](./docs/install_jitsi_google.md) 62 | 2. [安裝視訊系統前台](./docs/install_conferencesystem.md) 63 | 3. [客製化視訊系統](./docs/install_customization.md) 64 | 65 | ## 更多訊息 66 | 1. [awesome-jitsi 「Jitsi 資源大全」](https://github.com/Yuchunchen/awesome-jitsi) - 有許多很酷的應用,更歡迎您留下您的成品,大家互相觀摩! 67 | 2. [臺北榮民總醫院視訊門診系統](https://www.vghtpe.gov.tw/News!one.action?nid=6396) 68 | 3. [新北市國中小校長使用Jitsi](https://udn.com/news/story/7323/4498815) - 新北市泰山區同榮國小蔡明貴校長帶領「新北校長科技領導社群」率先使用Jitsi 69 | 70 | -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/.DS_Store -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | #常見問題 2 | -------------------------------------------------------------------------------- /docs/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/.DS_Store -------------------------------------------------------------------------------- /docs/images/add_my_domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/add_my_domain.png -------------------------------------------------------------------------------- /docs/images/desktop_screen010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen010.png -------------------------------------------------------------------------------- /docs/images/desktop_screen020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen020.png -------------------------------------------------------------------------------- /docs/images/desktop_screen030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen030.png -------------------------------------------------------------------------------- /docs/images/desktop_screen031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen031.png -------------------------------------------------------------------------------- /docs/images/desktop_screen040.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen040.png -------------------------------------------------------------------------------- /docs/images/desktop_screen041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen041.png -------------------------------------------------------------------------------- /docs/images/desktop_screen050.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen050.png -------------------------------------------------------------------------------- /docs/images/desktop_screen060.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/desktop_screen060.png -------------------------------------------------------------------------------- /docs/images/install_jitsi_azure_0010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_azure_0010.png -------------------------------------------------------------------------------- /docs/images/install_jitsi_azure_0020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_azure_0020.png -------------------------------------------------------------------------------- /docs/images/install_jitsi_dig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_dig.png -------------------------------------------------------------------------------- /docs/images/install_jitsi_dns_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_dns_example.png -------------------------------------------------------------------------------- /docs/images/install_jitsi_google0020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_google0020.png -------------------------------------------------------------------------------- /docs/images/install_jitsi_nslookup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/install_jitsi_nslookup.png -------------------------------------------------------------------------------- /docs/images/thesystem_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/docs/images/thesystem_architecture.png -------------------------------------------------------------------------------- /docs/install_conferencesystem.md: -------------------------------------------------------------------------------- 1 | # 如何安裝視訊系統前台 2 | 3 | - [如何安裝視訊系統前台](#如何安裝視訊系統前台) 4 | - [1. 從 Github 下載前台程式 ](#step1-從-Github-下載前台程式 ) 5 | - [2. 加入您的server](#step2-加入您的server) 6 | - [3. 收工了](#step2-收工了) 7 | - [4. 如何修改](#step3-如何修改) 8 | - [5. 重要系統設定檔案](#step4-重要系統設定檔案) 9 | 10 | --- 11 | 12 | 13 | ## step1. 從 Github 下載前台程式 14 | ```bash 15 | sudo git clone https://github.com/Yuchunchen/BuildYourOwnConferenceSystem.git ./local2 && 16 | sudo cp -arv ~/local2/src_meet_ym/*.* /usr/share/jitsi-meet/ && 17 | sudo cp -arv ~/local2/src_meet_ym/css /usr/share/jitsi-meet/ && 18 | sudo cp -arv ~/local2/src_meet_ym/images /usr/share/jitsi-meet/ && 19 | sudo cp -arv ~/local2/src_meet_ym/libs /usr/share/jitsi-meet/ 20 | ``` 21 | 22 | ## step2. 加入您的server 23 | 您可以先測試看看您所架設的jitsi server 是否可以順利運作: 24 | * 如果您安裝於Azure雲端主機,可以使用瀏覽器移至 https://myjitsidemo.westus2.cloudapp.azure.com/ 25 | * 如果您安裝於Google雲端,自己取了網域名稱(hosthname),您可以使用瀏覽器移至 https://hostname/ 26 | 可以看到啟動 jitsi 啟動畫面,代表您的主機已經順利啟動。 27 | 28 | 接著就是將您的主機加入視訊會議系統 meet_ym.html 網頁,請使用下列 nano 指令修改 第 44 列 (如下圖)後存檔 29 | ```bash 30 | sudo nano /usr/share/jitsi-meet/meet_ym.html 31 | ``` 32 | ![修改您的網域名稱](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/add_my_domain.png "修改您的網域名稱") 33 | 34 | ## step3. 收工了 35 | 是的,這樣就完成囉,趕快來看看安裝成果! 36 | 您可以先使用桌機或筆電, 37 | * 如果您安裝於Azure雲端主機, 38 | 您可以使用瀏覽器移至 https://myjitsidemo.westus2.cloudapp.azure.com/meet_ym.html 39 | * 如果您安裝於Google雲端,自己取了網域名稱(hosthname), 40 | 您可以使用瀏覽器移至 https://hostname/meet_ym.html 41 | 42 | 您也可以依照網頁說明下載APP,隨時隨地手機開會超方便! 43 | 44 | ## step4. 如何修改 45 | 根據這幾天回報,已經有許多朋友安裝成功,希望能夠更近一步修改為自己單位網頁,當然沒問題。 46 | 您只需要打開 meet_ym.html,您就可以修改網頁文字: 47 | ```bash 48 | sudo nano /usr/share/jitsi-meet/meet_ym.html 49 | ``` 50 | 51 | 更進階的修改,可以參考[進階客製化](./install_customization.md),您也可以參考[「Jitsi 資源大全」](https://github.com/Yuchunchen/awesome-jitsi)並且留下您的使用心得唷! 52 | 53 | 希望大家使用愉快! 54 | 55 | ## step5. 重要系統設定檔案 56 | Jitsi meet 還有很多神奇功能可以調整唷,底下是一些常用到的檔案位置,您一定要動手試試看! 57 | 依照您安裝的主機名稱(Azure:myjitsidemo.westus2.cloudapp.azure.com) ,檔名會有所不同,請注意替換唷! 58 | 59 | | |檔案 |說明|備註| 60 | |--------------|-------|---|---| 61 | |log |/var/log/jitsi/jvb.log ||| 62 | |log |/var/log/jitsi/jicofo.log ||| 63 | |log |/var/log/prosody/prosody.log ||| 64 | |JITSI設定檔 |/etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js|!注意主機名稱|| 65 | |JITSI介面設定檔|/usr/share/jitsi-meet/interface_config.js | || 66 | |nginx 設定 |/etc/nginx/sites-available/myjitsidemo.westus2.cloudapp.azure.com.conf|!注意主機名稱|重新啟動sudo systemctl restart nginx.service && sudo nano /etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js| 67 | | Web Server Entry |/usr/share/jitsi-meet/| Jitsi meet home page||| 68 | | 靜態大頭貼 |/usr/share/jitsi-meet/images/avatar2.png||| 69 | |JIBRI錄影檔 |/var/jbrecord ||| 70 | |JIBRI錄影上傳 |/home/jibri/finalize_recording.sh ||| 71 | -------------------------------------------------------------------------------- /docs/install_customization.md: -------------------------------------------------------------------------------- 1 | # 客製化Jitsi更符合您的需求 2 | 3 | - [客製化Jitsi更符合您的需求](#客製化Jitsi更符合您的需求) 4 | - [修改「陽明視訊系統」](#修改「陽明視訊系統」) 5 | - [修改首頁 Welcome page 文字](#修改首頁-welcome-page-文字) 6 | - [如何修改將錄影檔案上傳至雲端](#如何修改將錄影檔案上傳至雲端) 7 | - [更多連結](#更多連結) 8 | 9 | --- 10 | 11 | ## 修改「陽明視訊系統」 12 | 隨著上述步驟安裝,您應該可以直接以 https://您的jitsi名稱/meet_ym.html (如:https://jitsiym.aigia.ai/meet_ym.html ) 方式進入系統試用,當然,您可以直接利用下列指令進行任意修改: 13 | ```bash 14 | sudo nano /usr/share/jitsi-meet/meet_ym.html 15 | ``` 16 | 17 | ## 修改首頁 Welcome page 文字 18 | 19 | 有的朋友會希望可以修改官方的 welcome page,大概有兩種方法: 20 | 1. 官方版,建議由 github ( https://github.com/jitsi/jitsi-meet ) 下載原始碼,修改後再重新 compile 21 | 2. 快速版:直接修改 ```app.bundle.min.js``` 檔案: 22 | ```bash 23 | sudo nano /usr/share/jitsi-meet/libs/app.bundle.min.js 24 | ``` 25 | 26 | ## 如何修改將錄影檔案上傳至雲端 27 | 在完成錄影後,依照預設檔案會放置於 (/var/jbrecord) 目錄下,同時會執行 /home/jibri/finalize_recording.sh 命令,預設 finalize_recording.sh 不會進行任何動作,您可以自行修改此檔案。 (錄影與上傳相關設定檔案在 /etc/jitsi/jibri/config.json ) 28 | 29 | ##### 1. 利用 rclone 上傳至 Google, One drive 等雲端空間 30 | [rclone](https://rclone.org/docs/) 可以將檔案上傳至雲端空間(目前支援37種!),常見的Google, OneDrive, ftp, sftp, webdav 都在支援之列。這篇文章 [How-to to get a WORKING setup of Google Drive, One Drive or other cloud services in Jibri](https://community.jitsi.org/t/how-to-to-get-a-working-setup-of-google-drive-one-drive-or-other-cloud-services-in-jibri-my-comprehensive-tutorial-for-the-beginner/42228)是很重要的參考。 31 | 32 | ##### 2. 上傳到NAS系統,再透過NAS介面下載 33 | 可以參考 [這篇文章](https://community.jitsi.org/t/how-do-you-use-finalize-recordings-sh/28034/2),使用者[toni](https://community.jitsi.org/u/toni)分享了他的 finalize_recording.sh 如下: 34 | 35 | ```bash 36 | #!/bin/bash 37 | 38 | RECORDINGS_DIR=$1 39 | 40 | echo "$RECORDINGS_DIR" > /tmp/verzeichnis.out 41 | #echo "This is a dummy finalize script" > /tmp/finalize.out 42 | #echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /t$ 43 | #echo "You should put any finalize logic (renaming, uploading to a service" >> $ 44 | #echo "or storage provider, etc.) in this script" >> /tmp/finalize.out 45 | # The script exctracts the filename to prepare it for moving it to an external place, i.e. NAS drive 46 | 47 | pfad=$(cat /tmp/verzeichnis.out)"/*mp4" 48 | echo "$pfad" > /tmp/pfad.out 49 | ls -l $pfad > /tmp/filename.out 50 | filename=$(cat /tmp/filename.out) 51 | filenameneu=$(echo $filename | cut -c 47-) 52 | echo "$filenameneu" > tmp/filenameneu.out 53 | transfername=$(echo $filenameneu | cut -c 34-) 54 | echo "$transfername" > tmp/transfername.out 55 | # The syno.txt file contains the user password for the NAS drive 56 | sshpass -f /home/username/syno.txt scp $filenameneu username@NASdrivedomain.com/path/to/storagefolder/$transfername 57 | # Save a backup of the previous mp4 for safety reason 58 | cp /home/username/kopie.mp4 /home/username/kopie.mp4.bak 59 | # copy a backup of the actually moved mp4 to the local user folder 60 | cp $filenameneu /home/toni/kopie.mp4 61 | remove=$(cat /tmp/verzeichnis.out) 62 | 63 | rm -r $remove 64 | exit 0 65 | 66 | ``` 67 | 68 | ## 更多連結 69 | * 您可以參考[「Jitsi 資源大全」](https://github.com/Yuchunchen/awesome-jitsi),那裡有許多很酷的應用,更歡迎您留下您的成品,大家互相觀摩!世界更美好! 70 | * [easyjitsi](https://docs.easyjitsi.com/docs/config) 有些自訂化設定整理得不錯 71 | -------------------------------------------------------------------------------- /docs/install_jitsi_azure.md: -------------------------------------------------------------------------------- 1 | # 如何將 Jitsi meet 安裝於 Azure 雲端 2 | 3 | - [如何將 Jitsi meet 安裝於 Azure 雲端](#如何將-Jitsi-meet-安裝於-Azure-雲端) 4 | - [1. Azure 帳號](#step1-Azure-帳號) 5 | - [2. 建立雲端虛擬主機](#step2-建立雲端虛擬主機) 6 | - [3. 安裝 Jitsi meet 前置準備](#step3-安裝-Jitsi-meet-前置準備) 7 | - [4. 安裝 Jitsi meet](#step4-安裝-Jitsi-meet) 8 | - [5. 恭喜 :)](#step5-恭喜) 9 | - [6. 重要系統設定檔案](#step6-重要系統設定檔案) 10 | 11 | --- 12 | 13 | ## step1. Azure 帳號 14 | Jitsi meet對於硬體沒有特別要求,目前Azure提供12個月[免費使用](https://azure.microsoft.com/zh-tw/free/),再加上額外新台幣6100元試用金額,應該足以免費使用1年,是很好的開始。 15 | 16 | ## step2. 建立雲端虛擬主機 17 | 只需要5分鐘,就可以建立您的虛擬主機唷!請跟著下面影片,我們將在Azure雲端建立一台虛擬主機(名稱為: myjitsidemo),所需費用正好在Azure免費額度內,很適合預算有限的中小型機關學校。
18 | 19 | Azure VM 22 | 23 | 底下是過程中會需要用到的參數如下: 24 | 25 | |參數名稱|設定值|說明| 26 | |-------|-----|---| 27 | |資源群組|jitsidemo|| 28 | |電腦名稱|myjitsidemo|| 29 | |位置|美國西部2|目前這個位置的主機比較便宜| 30 | |登入帳號|jitsiadm|| 31 | |登入密碼| .....|請您自行設定一組12位密碼| 32 | |DNS名稱標籤|myjitsidemo|| 33 | |目的地連接埠範圍|80,443,4443,5222,5347,10000-20000|| 34 | 35 | 跟隨著影片步驟,您的主機應該看起來像下圖: 36 | ![虛擬主機](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/install_jitsi_azure_0010.png) 37 | 38 | 請記下下列資訊,接下來會常常使用到 39 | 40 | |參數名稱|設定值|說明| 41 | |-------|-----|---| 42 | |登入帳號|jitsiadm|| 43 | |登入密碼| .....|您剛剛自行設定的12位密碼| 44 | |DNS名稱|myjitsidemo.westus2.cloudapp.azure.com| 45 | |SSH登入指令|`ssh jitsiadm@myjitsidemo.westus2.cloudapp.azure.com`| 46 | 47 | ## step3. 安裝 Jitsi meet 前置準備 48 | 1. 您可以由畫面上面的連結(SSH 登入) 或者 左下角(序列主控台登入) 49 | ![登入](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/install_jitsi_azure_0020.png) 50 | 51 | 2. 系統更新 52 | * 更新 apt 53 | ```bash 54 | sudo apt update && 55 | sudo apt upgrade && 56 | sudo apt-get update && 57 | sudo apt-get install sshfs 58 | ``` 59 | * 安裝 nano 60 | ```bash 61 | sudo apt-get install nano 62 | ``` 63 | * 下載安裝標準 linux 核心: 下載 linux image 64 | ```bash 65 | sudo apt-get -y install linux-image-generic && 66 | sudo update-grub 67 | ``` 68 | * 開啟 /etc/default/grub 69 | ```bash 70 | sudo nano /etc/default/grub 71 | ``` 72 | * 設定 linux image 73 | 找到**GRUB_DEFAULT**,並修改為如下,Ctrl-X存檔 74 | `GRUB_DEFAULT='1>2'` 75 | * 重新開機 76 | ```bash 77 | sudo reboot now 78 | ``` 79 | 80 | ## step4. 安裝 Jitsi meet 81 | 82 | 1. 下載 Jitsi 安裝程式 83 | 雖然官方網站已提供安裝說明,但是仍有許多地方需要手工設定,經過測試,[SwITNet](https://github.com/switnet-ltd/quick-jibri-installer)所提供的快速安裝程序,可以省下不少設定帶來的挫折。 84 | ```bash 85 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jigasi.sh && 86 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jitsi-updater.sh && 87 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jm-bm.sh && 88 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jra_nextcloud.sh && 89 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/quick_jibri_installer.sh && 90 | chmod +x jigasi.sh && 91 | chmod +x jitsi-updater.sh && 92 | chmod +x jm-bm.sh && 93 | chmod +x jra_nextcloud.sh && 94 | chmod +x quick_jibri_installer.sh 95 | ``` 96 | 97 | 2. 執行 quick_jibri_installer.sh 98 | ```bash 99 | sudo ./quick_jibri_installer.sh 100 | ``` 101 | 102 | 3. 經過一小段時間下載更新後,接著我們要進行基本設定: 103 | 104 | | |提示|說明|建議回答|備註| 105 | |---|---|---|-------|---| 106 | |1.|Please set your language (Press enter to default to 'en')|選擇預設語系|zhTW|選擇中文,注意大小寫需一致)| 107 | |2.|Set sysadmin email|管理者電子郵件||此為必須欄位| 108 | |3.|Do you want to drop support for unsecure protocols TLSv1.0/1.1 now: (yes or no)|是否要停止支援不安全的http協定?|yes|我們希望系統僅能透過https加密連線| 109 | |4.|Do you want to setup LetsEncrypt with your domain: (yes or no)|是否需要SSL加密?|yes|| 110 | |5.|Do you want to setup the Dropbox feature now: (yes or no)|是否需要「錄影上傳dropbox」功能?|no|防止未經允許側錄會議| 111 | |6.|Do you want to install customized "brandless mode"?: (yes or no)|是否將Jitsi logo刪除?|no|建議保留jitsi logo| 112 | |7.|Do you want to translate 'Participant' to your own language? Leave empty to use the default one (English):|請將'Participant'翻譯成您的語言|Participant|不翻譯| 113 | |8.|Do you want to translate 'me' to your own language?|將 me 翻譯成您的語言|me|不翻譯| 114 | |9.|Do you want to disable the Welcome page: (yes or no)|是否關閉系統預設之首頁?|yes|我們將使用客製化首頁| 115 | |10.|Do you want to enable static avatar?: (yes or no)|是否使用固定大頭貼?|yes|可自行選擇| 116 | |11.|Do you want to enable local audio recording option?: (yes or no)|是否開放與會者自行側錄?|no|不允許側錄會議| 117 | |12.|Do you want to enable secure rooms?: (yes or no)|是否開啟會議室帳號密碼管控?|yes|| 118 | |13.|Set username for secure room moderator:|設定管理者帳號|ymteacher|請自行設定| 119 | |14.|Secure room moderator password:|管理者密碼|ym1234|請自行設定| 120 | |15.|Do you want to setup Jibri Records Access via Nextcloud: (yes or no)|是否開放雲端側錄?|no|不允許側錄會議| 121 | |16.|Do you want to setup Jigasi Transcription: (yes or no)|是否開啟自動語音辨識功能?|no|需要額外付費| 122 | 123 | 4. 再等待一下,系統將會自動重新啟動 124 | 125 | 5. 試試看使用Chrome 瀏覽器 瀏覽網址 https://myjitsidemo.westus2.cloudapp.azure.com 126 | 127 | 128 | ## step5. 恭喜 129 | 您現在應該已經有專屬於您的視訊平台囉,接著我們將[安裝視訊系統前台](./docs/install_conferencesystem.md),加上前端介面,讓系統更好用。 130 | 131 | ## step6. 重要系統設定檔案 132 | Jitsi meet 還有很多神奇功能可以調整唷,底下是一些常用到的檔案位置,您一定要動手試試看! 133 | 134 | | |檔案 |說明|備註| 135 | |--------------|-------|---|---| 136 | |log |/var/log/jitsi/jvb.log ||| 137 | |log |/var/log/jitsi/jicofo.log ||| 138 | |log |/var/log/prosody/prosody.log ||| 139 | |JITSI設定檔 |/etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js| || 140 | |JITSI介面設定檔|/usr/share/jitsi-meet/interface_config.js | || 141 | |nginx 設定 |/etc/nginx/sites-available/myjitsidemo.westus2.cloudapp.azure.com.conf||重新啟動sudo systemctl restart nginx.service && sudo nano /etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js| 142 | | Web Server Entry |/usr/share/jitsi-meet/| Jitsi meet home page||| 143 | | 靜態大頭貼 |/usr/share/jitsi-meet/images/avatar2.png||| 144 | |JIBRI錄影檔 |/var/jbrecord ||| 145 | |JIBRI錄影上傳 |/home/jibri/finalize_recording.sh ||| 146 | -------------------------------------------------------------------------------- /docs/install_jitsi_google.md: -------------------------------------------------------------------------------- 1 | # 如何將 Jitsi meet 安裝於 Google 雲端 2 | 3 | - [如何將 Jitsi meet 安裝於 Google 雲端](#如何將-Jitsi-meet-安裝於-Google-雲端) 4 | - [1. Google 帳號](#step1-Google-帳號) 5 | - [2. 建立Google雲端虛擬主機](#step2-建立Google雲端虛擬主機) 6 | - [3. 建立主機的網域名稱](#step3-建立主機的網域名稱domain-name) 7 | - [4. 安裝 Jitsi meet 前置準備](#step4-安裝-Jitsi-meet-前置準備) 8 | - [5. 安裝 Jitsi meet ](#step5-安裝-Jitsi-meet) 9 | - [6. 重要系統設定檔案](#step6-重要系統設定檔案) 10 | 11 | --- 12 | 13 | ## step1. Google 帳號 14 | Jitsi meet對於硬體沒有特別要求,目前Google提供12個月[免費使用](https://cloud.google.com/free/?hl=zh-TW),再加上額外美金300元試用金額,應該足以免費使用1年,是很好的開始。 15 | 16 | ## step2. 建立Google雲端虛擬主機 17 | 只需要6分鐘,就可以建立您的雲端虛擬主機。請跟著下面的影片操作,我們在Google雲端建立一台虛擬主機,所需費用正好在Google免費額度內,很適合預算有限的中小型機關學校。
18 | 19 | Azure VM 22 | 23 | 底下是過程中會需要用到的參數如下: 24 | 25 | |參數名稱|設定值|說明| 26 | |-------|-----|---| 27 | |專案名稱|jitsi-demo|| 28 | |電腦名稱|my-jitsi-demo|| 29 | |位置|台灣|放在台灣反應速度最佳| 30 | |開機磁碟|Ubuntu 18.04 LTS|作業系統| 31 | |保留靜態位址:名稱| ip-my-jitsi-demo || 32 | |保留靜態位址:**外部位址**|xxx.xxx.xxx.xxx|您會獲得一組固定IP(影片中範例為:104.199.168.254),請記下來| 33 | |建立防火牆規則:名稱| port-jitsi|| 34 | |建立防火牆規則:來源IP範圍| 0.0.0.0/0 |不設限,您可以視需要設定| 35 | |建立防火牆規則:指定的通訊協定和通訊埠|80,443,4443,5222,5347,10000-20000|tcp, udp都開啟以加快連線速度| 36 | 37 | 跟隨著影片步驟,您的主機應該看起來像下圖: 38 | ![虛擬主機](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/install_jitsi_google0020.png) 39 | 40 | 請記下下列資訊, 41 | 42 | |參數名稱|設定值|說明| 43 | |-------|-----|---| 44 | |外部IP位址 |(影片中範例為:104.199.168.254)|| 45 | 46 | 47 | ## step3. 建立主機的網域名稱domain name 48 | 您一定要設定網域名稱才能安裝jitsi系統。原因是jitsi meet會使用大量內部伺服器,數字型態IP位址無法使用。您必須跟學校/機關網路管理員申請,網路上有些免費網域服務伺服器可能可以試試看,有些學校有向Google購買G-suite服務,可能可以直接使用[G-suite名稱伺服器](https://support.google.com/a/answer/53929?hl=zh-Hant),或者您可以花一點錢(每年約新台幣300-2000元)直接向Google[購買一個專屬您的網域名稱](https://www.wfublog.com/2019/04/google-domains-tw-purchase-transfer-godaddy-dns.html),其他如[GoDaddy](https://tw.godaddy.com/domains/domain-name-search)、[Gandi](https://www.gandi.net/zh-Hant)..等都有提供便宜而且親善的中文介面。 49 | 50 | 無論您使用哪一家的網域名稱,重點在於您必須增加一筆 A紀錄 (A record),A記錄又稱為「地址記錄」(或主機記錄),可將網域連結至代管網域服務的主機實體 IP 位址。下面是我們使用網域伺服器使用畫面: 51 | 52 | ![DNS網域名稱設定](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/install_jitsi_dns_example.png) 53 | 54 | **!! 注意 !!** 一般網域名稱設定後,大約需要幾個小時才會生效,請您務必利用確認您的網域名稱生效後,才能繼續安裝。 55 | 您可以使用下列指令來確認網域名稱是否生效: 56 | ###### Windows 57 | ```nslookup 您的網域名稱``` (這裡以 sts.contoso.com 為例,截圖自微軟網頁) 58 | ![Windows](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/install_jitsi_nslookup.png) 59 | 60 | ###### Mac, Linux 61 | ```dig 您的網域名稱``` (這裡以陽明大學視訊系統 jitsiym.aigia.ai 為例) 62 | ![Mac](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/install_jitsi_dig.png) 63 | 64 | 65 | 66 | ## step4 安裝 Jitsi meet 前置準備 67 | 1. Google雲端很貼心,不需要另外設定,您可以點擊畫面的SSH直接登入主機: 68 | ![登入](https://github.com/Yuchunchen/BuildYourOwnConferenceSystem/blob/master/docs/images/install_jitsi_google0020.png) 69 | 70 | 2. 系統更新 71 | * 更新 apt 72 | ```bash 73 | sudo apt update && 74 | sudo apt upgrade && 75 | sudo apt-get update && 76 | sudo apt-get install sshfs 77 | ``` 78 | * 安裝 nano 79 | ```bash 80 | sudo apt-get install nano 81 | ``` 82 | * 下載安裝標準 linux 核心: 下載 linux image 83 | ```bash 84 | sudo apt-get -y install linux-image-generic && 85 | sudo update-grub 86 | ``` 87 | * 開啟 /etc/default/grub 88 | ```bash 89 | sudo nano /etc/default/grub 90 | ``` 91 | * 設定 linux image 92 | 找到**GRUB_DEFAULT**,並修改為如下,Ctrl-X存檔 93 | `GRUB_DEFAULT='1>2'` 94 | * 重新開機 95 | ```bash 96 | sudo reboot now 97 | ``` 98 | 99 | ## step5. 安裝 Jitsi meet 100 | 101 | 1. 下載 Jitsi 安裝程式 102 | 雖然官方網站已提供安裝說明,但是仍有許多地方需要手工設定,經過測試,[SwITNet](https://github.com/switnet-ltd/quick-jibri-installer)所提供的快速安裝程序,可以省下不少設定帶來的挫折。 103 | ```bash 104 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jigasi.sh && 105 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jitsi-updater.sh && 106 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jm-bm.sh && 107 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/jra_nextcloud.sh && 108 | wget https://raw.githubusercontent.com/switnet-ltd/quick-jibri-installer/master/quick_jibri_installer.sh && 109 | chmod +x jigasi.sh && 110 | chmod +x jitsi-updater.sh && 111 | chmod +x jm-bm.sh && 112 | chmod +x jra_nextcloud.sh && 113 | chmod +x quick_jibri_installer.sh 114 | ``` 115 | 116 | 2. 執行 quick_jibri_installer.sh 117 | ```bash 118 | sudo ./quick_jibri_installer.sh 119 | ``` 120 | 121 | 3. 經過一小段時間下載更新後,接著我們要進行基本設定: 122 | 123 | | |提示|說明|建議回答|備註| 124 | |---|---|---|-------|---| 125 | |1.|Please set your language (Press enter to default to 'en')|選擇預設語系|zhTW|選擇中文,注意大小寫需一致)| 126 | |2.|Set sysadmin email|管理者電子郵件||此為必須欄位| 127 | |3.|Do you want to drop support for unsecure protocols TLSv1.0/1.1 now: (yes or no)|是否要停止支援不安全的http協定?|yes|我們希望系統僅能透過https加密連線| 128 | |4.|Do you want to setup LetsEncrypt with your domain: (yes or no)|是否需要SSL加密?|yes|| 129 | |5.|Do you want to setup the Dropbox feature now: (yes or no)|是否需要「錄影上傳dropbox」功能?|no|防止未經允許側錄會議| 130 | |6.|Do you want to install customized "brandless mode"?: (yes or no)|是否將Jitsi logo刪除?|no|建議保留jitsi logo| 131 | |7.|Do you want to translate 'Participant' to your own language? Leave empty to use the default one (English):|請將'Participant' 翻譯成您的語言|Participant|不翻譯| 132 | |8.|Do you want to translate 'me' to your own language?|將 me 翻譯成您的語言|me|不翻譯| 133 | |9.|Do you want to disable the Welcome page: (yes or no)|是否關閉系統預設之首頁?|yes|我們將使用客製化首頁| 134 | |10.|Do you want to enable static avatar?: (yes or no)|是否使用固定大頭貼?|yes|可自行選擇| 135 | |11.|Do you want to enable local audio recording option?: (yes or no)|是否開放與會者自行側錄?|no|不允許側錄會議| 136 | |12.|Do you want to enable secure rooms?: (yes or no)|是否開啟會議室帳號密碼管控?|yes|| 137 | |13.|Set username for secure room moderator:|設定管理者帳號|ymteacher|請自行設定| 138 | |14.|Secure room moderator password:|管理者密碼|ym1234|請自行設定| 139 | |15.|Do you want to setup Jibri Records Access via Nextcloud: (yes or no)|是否開放雲端側錄?|no|不允許側錄會議| 140 | |16.|Do you want to setup Jigasi Transcription: (yes or no)|是否開啟自動語音辨識功能?|no|需要額外付費| 141 | 142 | 4. 再等待一下,系統將會自動重新啟動 143 | 144 | 5. 試試看使用Chrome 瀏覽器 瀏覽看看唷! 145 | 146 | 147 | ## step6. 重要系統設定檔案 148 | 149 | | |檔案 |說明|備註| 150 | |--------------|-------|---|---| 151 | |log |/var/log/jitsi/jvb.log ||| 152 | |log |/var/log/jitsi/jicofo.log ||| 153 | |log |/var/log/prosody/prosody.log ||| 154 | |JITSI設定檔 |/etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js| || 155 | |JITSI介面設定檔|/usr/share/jitsi-meet/interface_config.js | || 156 | |nginx 設定 |/etc/nginx/sites-available/myjitsidemo.westus2.cloudapp.azure.com.conf||重新啟動sudo systemctl restart nginx.service && sudo nano /etc/jitsi/meet/myjitsidemo.westus2.cloudapp.azure.com-config.js| 157 | | Web Server Entry |/usr/share/jitsi-meet/| Jitsi meet home page||| 158 | | 靜態大頭貼 |/usr/share/jitsi-meet/images/avatar2.png||| 159 | |JIBRI錄影檔 | /var/jbrecord ||| 160 | |JIBRI錄影上傳 | /home/jibri/finalize_recording.sh ||| 161 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | #STEP1. 安裝您的Jitsi視訊主機 2 | -------------------------------------------------------------------------------- /src_meet_ym/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/.DS_Store -------------------------------------------------------------------------------- /src_meet_ym/YM_JITSI_操作說明.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/YM_JITSI_操作說明.pdf -------------------------------------------------------------------------------- /src_meet_ym/css/waiting.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/css/waiting.gif -------------------------------------------------------------------------------- /src_meet_ym/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/.DS_Store -------------------------------------------------------------------------------- /src_meet_ym/images/aigia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/aigia.png -------------------------------------------------------------------------------- /src_meet_ym/images/browser_setting.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/browser_setting.mp4 -------------------------------------------------------------------------------- /src_meet_ym/images/mail/contact_me.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | $("#contactForm input,#contactForm textarea").jqBootstrapValidation({ 4 | preventSubmit: true, 5 | submitError: function($form, event, errors) { 6 | // additional error messages or events 7 | }, 8 | submitSuccess: function($form, event) { 9 | event.preventDefault(); // prevent default submit behaviour 10 | // get values from FORM 11 | var name = $("input#name").val(); 12 | var email = $("input#email").val(); 13 | var phone = $("input#phone").val(); 14 | var message = $("textarea#message").val(); 15 | var firstName = name; // For Success/Failure Message 16 | // Check for white space in name for Success/Fail message 17 | if (firstName.indexOf(' ') >= 0) { 18 | firstName = name.split(' ').slice(0, -1).join(' '); 19 | } 20 | $this = $("#sendMessageButton"); 21 | $this.prop("disabled", true); // Disable submit button until AJAX call is complete to prevent duplicate messages 22 | $.ajax({ 23 | url: "contact_me.php", 24 | type: "POST", 25 | data: { 26 | name: name, 27 | phone: phone, 28 | email: email, 29 | message: message 30 | }, 31 | cache: false, 32 | success: function() { 33 | // Success message 34 | $('#success').html("
"); 35 | $('#success > .alert-success').html(""); 37 | $('#success > .alert-success') 38 | .append("Your message has been sent. "); 39 | $('#success > .alert-success') 40 | .append('
'); 41 | //clear all fields 42 | $('#contactForm').trigger("reset"); 43 | }, 44 | error: function() { 45 | // Fail message 46 | $('#success').html("
"); 47 | $('#success > .alert-danger').html(""); 49 | $('#success > .alert-danger').append($("").text("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!")); 50 | $('#success > .alert-danger').append('
'); 51 | //clear all fields 52 | $('#contactForm').trigger("reset"); 53 | }, 54 | complete: function() { 55 | setTimeout(function() { 56 | $this.prop("disabled", false); // Re-enable submit button when AJAX call is complete 57 | }, 1000); 58 | } 59 | }); 60 | }, 61 | filter: function() { 62 | return $(this).is(":visible"); 63 | }, 64 | }); 65 | 66 | $("a[data-toggle=\"tab\"]").click(function(e) { 67 | e.preventDefault(); 68 | $(this).tab("show"); 69 | }); 70 | }); 71 | 72 | /*When clicking on Full hide fail/success boxes */ 73 | $('#name').focus(function() { 74 | $('#success').html(''); 75 | }); 76 | -------------------------------------------------------------------------------- /src_meet_ym/images/mail/contact_me.php: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /src_meet_ym/images/mail/jqBootstrapValidation.js: -------------------------------------------------------------------------------- 1 | /* jqBootstrapValidation 2 | * A plugin for automating validation on Twitter Bootstrap formatted forms. 3 | * 4 | * v1.3.6 5 | * 6 | * License: MIT - see LICENSE file 7 | * 8 | * http://ReactiveRaven.github.com/jqBootstrapValidation/ 9 | */ 10 | 11 | (function( $ ){ 12 | 13 | var createdElements = []; 14 | 15 | var defaults = { 16 | options: { 17 | prependExistingHelpBlock: false, 18 | sniffHtml: true, // sniff for 'required', 'maxlength', etc 19 | preventSubmit: true, // stop the form submit event from firing if validation fails 20 | submitError: false, // function called if there is an error when trying to submit 21 | submitSuccess: false, // function called just before a successful submit event is sent to the server 22 | semanticallyStrict: false, // set to true to tidy up generated HTML output 23 | autoAdd: { 24 | helpBlocks: true 25 | }, 26 | filter: function () { 27 | // return $(this).is(":visible"); // only validate elements you can see 28 | return true; // validate everything 29 | } 30 | }, 31 | methods: { 32 | init : function( options ) { 33 | 34 | var settings = $.extend(true, {}, defaults); 35 | 36 | settings.options = $.extend(true, settings.options, options); 37 | 38 | var $siblingElements = this; 39 | 40 | var uniqueForms = $.unique( 41 | $siblingElements.map( function () { 42 | return $(this).parents("form")[0]; 43 | }).toArray() 44 | ); 45 | 46 | $(uniqueForms).bind("submit", function (e) { 47 | var $form = $(this); 48 | var warningsFound = 0; 49 | var $inputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter); 50 | $inputs.trigger("submit.validation").trigger("validationLostFocus.validation"); 51 | 52 | $inputs.each(function (i, el) { 53 | var $this = $(el), 54 | $controlGroup = $this.parents(".control-group").first(); 55 | if ( 56 | $controlGroup.hasClass("warning") 57 | ) { 58 | $controlGroup.removeClass("warning").addClass("error"); 59 | warningsFound++; 60 | } 61 | }); 62 | 63 | $inputs.trigger("validationLostFocus.validation"); 64 | 65 | if (warningsFound) { 66 | if (settings.options.preventSubmit) { 67 | e.preventDefault(); 68 | } 69 | $form.addClass("error"); 70 | if ($.isFunction(settings.options.submitError)) { 71 | settings.options.submitError($form, e, $inputs.jqBootstrapValidation("collectErrors", true)); 72 | } 73 | } else { 74 | $form.removeClass("error"); 75 | if ($.isFunction(settings.options.submitSuccess)) { 76 | settings.options.submitSuccess($form, e); 77 | } 78 | } 79 | }); 80 | 81 | return this.each(function(){ 82 | 83 | // Get references to everything we're interested in 84 | var $this = $(this), 85 | $controlGroup = $this.parents(".control-group").first(), 86 | $helpBlock = $controlGroup.find(".help-block").first(), 87 | $form = $this.parents("form").first(), 88 | validatorNames = []; 89 | 90 | // create message container if not exists 91 | if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) { 92 | $helpBlock = $('
'); 93 | $controlGroup.find('.controls').append($helpBlock); 94 | createdElements.push($helpBlock[0]); 95 | } 96 | 97 | // ============================================================= 98 | // SNIFF HTML FOR VALIDATORS 99 | // ============================================================= 100 | 101 | // *snort sniff snuffle* 102 | 103 | if (settings.options.sniffHtml) { 104 | var message = ""; 105 | // --------------------------------------------------------- 106 | // PATTERN 107 | // --------------------------------------------------------- 108 | if ($this.attr("pattern") !== undefined) { 109 | message = "Not in the expected format"; 110 | if ($this.data("validationPatternMessage")) { 111 | message = $this.data("validationPatternMessage"); 112 | } 113 | $this.data("validationPatternMessage", message); 114 | $this.data("validationPatternRegex", $this.attr("pattern")); 115 | } 116 | // --------------------------------------------------------- 117 | // MAX 118 | // --------------------------------------------------------- 119 | if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) { 120 | var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax")); 121 | message = "Too high: Maximum of '" + max + "'"; 122 | if ($this.data("validationMaxMessage")) { 123 | message = $this.data("validationMaxMessage"); 124 | } 125 | $this.data("validationMaxMessage", message); 126 | $this.data("validationMaxMax", max); 127 | } 128 | // --------------------------------------------------------- 129 | // MIN 130 | // --------------------------------------------------------- 131 | if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) { 132 | var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin")); 133 | message = "Too low: Minimum of '" + min + "'"; 134 | if ($this.data("validationMinMessage")) { 135 | message = $this.data("validationMinMessage"); 136 | } 137 | $this.data("validationMinMessage", message); 138 | $this.data("validationMinMin", min); 139 | } 140 | // --------------------------------------------------------- 141 | // MAXLENGTH 142 | // --------------------------------------------------------- 143 | if ($this.attr("maxlength") !== undefined) { 144 | message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters"; 145 | if ($this.data("validationMaxlengthMessage")) { 146 | message = $this.data("validationMaxlengthMessage"); 147 | } 148 | $this.data("validationMaxlengthMessage", message); 149 | $this.data("validationMaxlengthMaxlength", $this.attr("maxlength")); 150 | } 151 | // --------------------------------------------------------- 152 | // MINLENGTH 153 | // --------------------------------------------------------- 154 | if ($this.attr("minlength") !== undefined) { 155 | message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters"; 156 | if ($this.data("validationMinlengthMessage")) { 157 | message = $this.data("validationMinlengthMessage"); 158 | } 159 | $this.data("validationMinlengthMessage", message); 160 | $this.data("validationMinlengthMinlength", $this.attr("minlength")); 161 | } 162 | // --------------------------------------------------------- 163 | // REQUIRED 164 | // --------------------------------------------------------- 165 | if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) { 166 | message = settings.builtInValidators.required.message; 167 | if ($this.data("validationRequiredMessage")) { 168 | message = $this.data("validationRequiredMessage"); 169 | } 170 | $this.data("validationRequiredMessage", message); 171 | } 172 | // --------------------------------------------------------- 173 | // NUMBER 174 | // --------------------------------------------------------- 175 | if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") { 176 | message = settings.builtInValidators.number.message; 177 | if ($this.data("validationNumberMessage")) { 178 | message = $this.data("validationNumberMessage"); 179 | } 180 | $this.data("validationNumberMessage", message); 181 | } 182 | // --------------------------------------------------------- 183 | // EMAIL 184 | // --------------------------------------------------------- 185 | if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") { 186 | message = "Not a valid email address"; 187 | if ($this.data("validationValidemailMessage")) { 188 | message = $this.data("validationValidemailMessage"); 189 | } else if ($this.data("validationEmailMessage")) { 190 | message = $this.data("validationEmailMessage"); 191 | } 192 | $this.data("validationValidemailMessage", message); 193 | } 194 | // --------------------------------------------------------- 195 | // MINCHECKED 196 | // --------------------------------------------------------- 197 | if ($this.attr("minchecked") !== undefined) { 198 | message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required"; 199 | if ($this.data("validationMincheckedMessage")) { 200 | message = $this.data("validationMincheckedMessage"); 201 | } 202 | $this.data("validationMincheckedMessage", message); 203 | $this.data("validationMincheckedMinchecked", $this.attr("minchecked")); 204 | } 205 | // --------------------------------------------------------- 206 | // MAXCHECKED 207 | // --------------------------------------------------------- 208 | if ($this.attr("maxchecked") !== undefined) { 209 | message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required"; 210 | if ($this.data("validationMaxcheckedMessage")) { 211 | message = $this.data("validationMaxcheckedMessage"); 212 | } 213 | $this.data("validationMaxcheckedMessage", message); 214 | $this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked")); 215 | } 216 | } 217 | 218 | // ============================================================= 219 | // COLLECT VALIDATOR NAMES 220 | // ============================================================= 221 | 222 | // Get named validators 223 | if ($this.data("validation") !== undefined) { 224 | validatorNames = $this.data("validation").split(","); 225 | } 226 | 227 | // Get extra ones defined on the element's data attributes 228 | $.each($this.data(), function (i, el) { 229 | var parts = i.replace(/([A-Z])/g, ",$1").split(","); 230 | if (parts[0] === "validation" && parts[1]) { 231 | validatorNames.push(parts[1]); 232 | } 233 | }); 234 | 235 | // ============================================================= 236 | // NORMALISE VALIDATOR NAMES 237 | // ============================================================= 238 | 239 | var validatorNamesToInspect = validatorNames; 240 | var newValidatorNamesToInspect = []; 241 | 242 | do // repeatedly expand 'shortcut' validators into their real validators 243 | { 244 | // Uppercase only the first letter of each name 245 | $.each(validatorNames, function (i, el) { 246 | validatorNames[i] = formatValidatorName(el); 247 | }); 248 | 249 | // Remove duplicate validator names 250 | validatorNames = $.unique(validatorNames); 251 | 252 | // Pull out the new validator names from each shortcut 253 | newValidatorNamesToInspect = []; 254 | $.each(validatorNamesToInspect, function(i, el) { 255 | if ($this.data("validation" + el + "Shortcut") !== undefined) { 256 | // Are these custom validators? 257 | // Pull them out! 258 | $.each($this.data("validation" + el + "Shortcut").split(","), function(i2, el2) { 259 | newValidatorNamesToInspect.push(el2); 260 | }); 261 | } else if (settings.builtInValidators[el.toLowerCase()]) { 262 | // Is this a recognised built-in? 263 | // Pull it out! 264 | var validator = settings.builtInValidators[el.toLowerCase()]; 265 | if (validator.type.toLowerCase() === "shortcut") { 266 | $.each(validator.shortcut.split(","), function (i, el) { 267 | el = formatValidatorName(el); 268 | newValidatorNamesToInspect.push(el); 269 | validatorNames.push(el); 270 | }); 271 | } 272 | } 273 | }); 274 | 275 | validatorNamesToInspect = newValidatorNamesToInspect; 276 | 277 | } while (validatorNamesToInspect.length > 0) 278 | 279 | // ============================================================= 280 | // SET UP VALIDATOR ARRAYS 281 | // ============================================================= 282 | 283 | var validators = {}; 284 | 285 | $.each(validatorNames, function (i, el) { 286 | // Set up the 'override' message 287 | var message = $this.data("validation" + el + "Message"); 288 | var hasOverrideMessage = (message !== undefined); 289 | var foundValidator = false; 290 | message = 291 | ( 292 | message 293 | ? message 294 | : "'" + el + "' validation failed " 295 | ) 296 | ; 297 | 298 | $.each( 299 | settings.validatorTypes, 300 | function (validatorType, validatorTemplate) { 301 | if (validators[validatorType] === undefined) { 302 | validators[validatorType] = []; 303 | } 304 | if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) { 305 | validators[validatorType].push( 306 | $.extend( 307 | true, 308 | { 309 | name: formatValidatorName(validatorTemplate.name), 310 | message: message 311 | }, 312 | validatorTemplate.init($this, el) 313 | ) 314 | ); 315 | foundValidator = true; 316 | } 317 | } 318 | ); 319 | 320 | if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) { 321 | 322 | var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]); 323 | if (hasOverrideMessage) { 324 | validator.message = message; 325 | } 326 | var validatorType = validator.type.toLowerCase(); 327 | 328 | if (validatorType === "shortcut") { 329 | foundValidator = true; 330 | } else { 331 | $.each( 332 | settings.validatorTypes, 333 | function (validatorTemplateType, validatorTemplate) { 334 | if (validators[validatorTemplateType] === undefined) { 335 | validators[validatorTemplateType] = []; 336 | } 337 | if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) { 338 | $this.data("validation" + el + formatValidatorName(validatorTemplate.name), validator[validatorTemplate.name.toLowerCase()]); 339 | validators[validatorType].push( 340 | $.extend( 341 | validator, 342 | validatorTemplate.init($this, el) 343 | ) 344 | ); 345 | foundValidator = true; 346 | } 347 | } 348 | ); 349 | } 350 | } 351 | 352 | if (! foundValidator) { 353 | $.error("Cannot find validation info for '" + el + "'"); 354 | } 355 | }); 356 | 357 | // ============================================================= 358 | // STORE FALLBACK VALUES 359 | // ============================================================= 360 | 361 | $helpBlock.data( 362 | "original-contents", 363 | ( 364 | $helpBlock.data("original-contents") 365 | ? $helpBlock.data("original-contents") 366 | : $helpBlock.html() 367 | ) 368 | ); 369 | 370 | $helpBlock.data( 371 | "original-role", 372 | ( 373 | $helpBlock.data("original-role") 374 | ? $helpBlock.data("original-role") 375 | : $helpBlock.attr("role") 376 | ) 377 | ); 378 | 379 | $controlGroup.data( 380 | "original-classes", 381 | ( 382 | $controlGroup.data("original-clases") 383 | ? $controlGroup.data("original-classes") 384 | : $controlGroup.attr("class") 385 | ) 386 | ); 387 | 388 | $this.data( 389 | "original-aria-invalid", 390 | ( 391 | $this.data("original-aria-invalid") 392 | ? $this.data("original-aria-invalid") 393 | : $this.attr("aria-invalid") 394 | ) 395 | ); 396 | 397 | // ============================================================= 398 | // VALIDATION 399 | // ============================================================= 400 | 401 | $this.bind( 402 | "validation.validation", 403 | function (event, params) { 404 | 405 | var value = getValue($this); 406 | 407 | // Get a list of the errors to apply 408 | var errorsFound = []; 409 | 410 | $.each(validators, function (validatorType, validatorTypeArray) { 411 | if (value || value.length || (params && params.includeEmpty) || (!!settings.validatorTypes[validatorType].blockSubmit && params && !!params.submitting)) { 412 | $.each(validatorTypeArray, function (i, validator) { 413 | if (settings.validatorTypes[validatorType].validate($this, value, validator)) { 414 | errorsFound.push(validator.message); 415 | } 416 | }); 417 | } 418 | }); 419 | 420 | return errorsFound; 421 | } 422 | ); 423 | 424 | $this.bind( 425 | "getValidators.validation", 426 | function () { 427 | return validators; 428 | } 429 | ); 430 | 431 | // ============================================================= 432 | // WATCH FOR CHANGES 433 | // ============================================================= 434 | $this.bind( 435 | "submit.validation", 436 | function () { 437 | return $this.triggerHandler("change.validation", {submitting: true}); 438 | } 439 | ); 440 | $this.bind( 441 | [ 442 | "keyup", 443 | "focus", 444 | "blur", 445 | "click", 446 | "keydown", 447 | "keypress", 448 | "change" 449 | ].join(".validation ") + ".validation", 450 | function (e, params) { 451 | 452 | var value = getValue($this); 453 | 454 | var errorsFound = []; 455 | 456 | $controlGroup.find("input,textarea,select").each(function (i, el) { 457 | var oldCount = errorsFound.length; 458 | $.each($(el).triggerHandler("validation.validation", params), function (j, message) { 459 | errorsFound.push(message); 460 | }); 461 | if (errorsFound.length > oldCount) { 462 | $(el).attr("aria-invalid", "true"); 463 | } else { 464 | var original = $this.data("original-aria-invalid"); 465 | $(el).attr("aria-invalid", (original !== undefined ? original : false)); 466 | } 467 | }); 468 | 469 | $form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation"); 470 | 471 | errorsFound = $.unique(errorsFound.sort()); 472 | 473 | // Were there any errors? 474 | if (errorsFound.length) { 475 | // Better flag it up as a warning. 476 | $controlGroup.removeClass("success error").addClass("warning"); 477 | 478 | // How many errors did we find? 479 | if (settings.options.semanticallyStrict && errorsFound.length === 1) { 480 | // Only one? Being strict? Just output it. 481 | $helpBlock.html(errorsFound[0] + 482 | ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" )); 483 | } else { 484 | // Multiple? Being sloppy? Glue them together into an UL. 485 | $helpBlock.html("
  • " + errorsFound.join("
  • ") + "
" + 486 | ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" )); 487 | } 488 | } else { 489 | $controlGroup.removeClass("warning error success"); 490 | if (value.length > 0) { 491 | $controlGroup.addClass("success"); 492 | } 493 | $helpBlock.html($helpBlock.data("original-contents")); 494 | } 495 | 496 | if (e.type === "blur") { 497 | $controlGroup.removeClass("success"); 498 | } 499 | } 500 | ); 501 | $this.bind("validationLostFocus.validation", function () { 502 | $controlGroup.removeClass("success"); 503 | }); 504 | }); 505 | }, 506 | destroy : function( ) { 507 | 508 | return this.each( 509 | function() { 510 | 511 | var 512 | $this = $(this), 513 | $controlGroup = $this.parents(".control-group").first(), 514 | $helpBlock = $controlGroup.find(".help-block").first(); 515 | 516 | // remove our events 517 | $this.unbind('.validation'); // events are namespaced. 518 | // reset help text 519 | $helpBlock.html($helpBlock.data("original-contents")); 520 | // reset classes 521 | $controlGroup.attr("class", $controlGroup.data("original-classes")); 522 | // reset aria 523 | $this.attr("aria-invalid", $this.data("original-aria-invalid")); 524 | // reset role 525 | $helpBlock.attr("role", $this.data("original-role")); 526 | // remove all elements we created 527 | if (createdElements.indexOf($helpBlock[0]) > -1) { 528 | $helpBlock.remove(); 529 | } 530 | 531 | } 532 | ); 533 | 534 | }, 535 | collectErrors : function(includeEmpty) { 536 | 537 | var errorMessages = {}; 538 | this.each(function (i, el) { 539 | var $el = $(el); 540 | var name = $el.attr("name"); 541 | var errors = $el.triggerHandler("validation.validation", {includeEmpty: true}); 542 | errorMessages[name] = $.extend(true, errors, errorMessages[name]); 543 | }); 544 | 545 | $.each(errorMessages, function (i, el) { 546 | if (el.length === 0) { 547 | delete errorMessages[i]; 548 | } 549 | }); 550 | 551 | return errorMessages; 552 | 553 | }, 554 | hasErrors: function() { 555 | 556 | var errorMessages = []; 557 | 558 | this.each(function (i, el) { 559 | errorMessages = errorMessages.concat( 560 | $(el).triggerHandler("getValidators.validation") ? $(el).triggerHandler("validation.validation", {submitting: true}) : [] 561 | ); 562 | }); 563 | 564 | return (errorMessages.length > 0); 565 | }, 566 | override : function (newDefaults) { 567 | defaults = $.extend(true, defaults, newDefaults); 568 | } 569 | }, 570 | validatorTypes: { 571 | callback: { 572 | name: "callback", 573 | init: function ($this, name) { 574 | return { 575 | validatorName: name, 576 | callback: $this.data("validation" + name + "Callback"), 577 | lastValue: $this.val(), 578 | lastValid: true, 579 | lastFinished: true 580 | }; 581 | }, 582 | validate: function ($this, value, validator) { 583 | if (validator.lastValue === value && validator.lastFinished) { 584 | return !validator.lastValid; 585 | } 586 | 587 | if (validator.lastFinished === true) 588 | { 589 | validator.lastValue = value; 590 | validator.lastValid = true; 591 | validator.lastFinished = false; 592 | 593 | var rrjqbvValidator = validator; 594 | var rrjqbvThis = $this; 595 | executeFunctionByName( 596 | validator.callback, 597 | window, 598 | $this, 599 | value, 600 | function (data) { 601 | if (rrjqbvValidator.lastValue === data.value) { 602 | rrjqbvValidator.lastValid = data.valid; 603 | if (data.message) { 604 | rrjqbvValidator.message = data.message; 605 | } 606 | rrjqbvValidator.lastFinished = true; 607 | rrjqbvThis.data("validation" + rrjqbvValidator.validatorName + "Message", rrjqbvValidator.message); 608 | // Timeout is set to avoid problems with the events being considered 'already fired' 609 | setTimeout(function () { 610 | rrjqbvThis.trigger("change.validation"); 611 | }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst 612 | } 613 | } 614 | ); 615 | } 616 | 617 | return false; 618 | 619 | } 620 | }, 621 | ajax: { 622 | name: "ajax", 623 | init: function ($this, name) { 624 | return { 625 | validatorName: name, 626 | url: $this.data("validation" + name + "Ajax"), 627 | lastValue: $this.val(), 628 | lastValid: true, 629 | lastFinished: true 630 | }; 631 | }, 632 | validate: function ($this, value, validator) { 633 | if (""+validator.lastValue === ""+value && validator.lastFinished === true) { 634 | return validator.lastValid === false; 635 | } 636 | 637 | if (validator.lastFinished === true) 638 | { 639 | validator.lastValue = value; 640 | validator.lastValid = true; 641 | validator.lastFinished = false; 642 | $.ajax({ 643 | url: validator.url, 644 | data: "value=" + value + "&field=" + $this.attr("name"), 645 | dataType: "json", 646 | success: function (data) { 647 | if (""+validator.lastValue === ""+data.value) { 648 | validator.lastValid = !!(data.valid); 649 | if (data.message) { 650 | validator.message = data.message; 651 | } 652 | validator.lastFinished = true; 653 | $this.data("validation" + validator.validatorName + "Message", validator.message); 654 | // Timeout is set to avoid problems with the events being considered 'already fired' 655 | setTimeout(function () { 656 | $this.trigger("change.validation"); 657 | }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst 658 | } 659 | }, 660 | failure: function () { 661 | validator.lastValid = true; 662 | validator.message = "ajax call failed"; 663 | validator.lastFinished = true; 664 | $this.data("validation" + validator.validatorName + "Message", validator.message); 665 | // Timeout is set to avoid problems with the events being considered 'already fired' 666 | setTimeout(function () { 667 | $this.trigger("change.validation"); 668 | }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst 669 | } 670 | }); 671 | } 672 | 673 | return false; 674 | 675 | } 676 | }, 677 | regex: { 678 | name: "regex", 679 | init: function ($this, name) { 680 | return {regex: regexFromString($this.data("validation" + name + "Regex"))}; 681 | }, 682 | validate: function ($this, value, validator) { 683 | return (!validator.regex.test(value) && ! validator.negative) 684 | || (validator.regex.test(value) && validator.negative); 685 | } 686 | }, 687 | required: { 688 | name: "required", 689 | init: function ($this, name) { 690 | return {}; 691 | }, 692 | validate: function ($this, value, validator) { 693 | return !!(value.length === 0 && ! validator.negative) 694 | || !!(value.length > 0 && validator.negative); 695 | }, 696 | blockSubmit: true 697 | }, 698 | match: { 699 | name: "match", 700 | init: function ($this, name) { 701 | var element = $this.parents("form").first().find("[name=\"" + $this.data("validation" + name + "Match") + "\"]").first(); 702 | element.bind("validation.validation", function () { 703 | $this.trigger("change.validation", {submitting: true}); 704 | }); 705 | return {"element": element}; 706 | }, 707 | validate: function ($this, value, validator) { 708 | return (value !== validator.element.val() && ! validator.negative) 709 | || (value === validator.element.val() && validator.negative); 710 | }, 711 | blockSubmit: true 712 | }, 713 | max: { 714 | name: "max", 715 | init: function ($this, name) { 716 | return {max: $this.data("validation" + name + "Max")}; 717 | }, 718 | validate: function ($this, value, validator) { 719 | return (parseFloat(value, 10) > parseFloat(validator.max, 10) && ! validator.negative) 720 | || (parseFloat(value, 10) <= parseFloat(validator.max, 10) && validator.negative); 721 | } 722 | }, 723 | min: { 724 | name: "min", 725 | init: function ($this, name) { 726 | return {min: $this.data("validation" + name + "Min")}; 727 | }, 728 | validate: function ($this, value, validator) { 729 | return (parseFloat(value) < parseFloat(validator.min) && ! validator.negative) 730 | || (parseFloat(value) >= parseFloat(validator.min) && validator.negative); 731 | } 732 | }, 733 | maxlength: { 734 | name: "maxlength", 735 | init: function ($this, name) { 736 | return {maxlength: $this.data("validation" + name + "Maxlength")}; 737 | }, 738 | validate: function ($this, value, validator) { 739 | return ((value.length > validator.maxlength) && ! validator.negative) 740 | || ((value.length <= validator.maxlength) && validator.negative); 741 | } 742 | }, 743 | minlength: { 744 | name: "minlength", 745 | init: function ($this, name) { 746 | return {minlength: $this.data("validation" + name + "Minlength")}; 747 | }, 748 | validate: function ($this, value, validator) { 749 | return ((value.length < validator.minlength) && ! validator.negative) 750 | || ((value.length >= validator.minlength) && validator.negative); 751 | } 752 | }, 753 | maxchecked: { 754 | name: "maxchecked", 755 | init: function ($this, name) { 756 | var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]"); 757 | elements.bind("click.validation", function () { 758 | $this.trigger("change.validation", {includeEmpty: true}); 759 | }); 760 | return {maxchecked: $this.data("validation" + name + "Maxchecked"), elements: elements}; 761 | }, 762 | validate: function ($this, value, validator) { 763 | return (validator.elements.filter(":checked").length > validator.maxchecked && ! validator.negative) 764 | || (validator.elements.filter(":checked").length <= validator.maxchecked && validator.negative); 765 | }, 766 | blockSubmit: true 767 | }, 768 | minchecked: { 769 | name: "minchecked", 770 | init: function ($this, name) { 771 | var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]"); 772 | elements.bind("click.validation", function () { 773 | $this.trigger("change.validation", {includeEmpty: true}); 774 | }); 775 | return {minchecked: $this.data("validation" + name + "Minchecked"), elements: elements}; 776 | }, 777 | validate: function ($this, value, validator) { 778 | return (validator.elements.filter(":checked").length < validator.minchecked && ! validator.negative) 779 | || (validator.elements.filter(":checked").length >= validator.minchecked && validator.negative); 780 | }, 781 | blockSubmit: true 782 | } 783 | }, 784 | builtInValidators: { 785 | email: { 786 | name: "Email", 787 | type: "shortcut", 788 | shortcut: "validemail" 789 | }, 790 | validemail: { 791 | name: "Validemail", 792 | type: "regex", 793 | regex: "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\.[A-Za-z]{2,4}", 794 | message: "Not a valid email address" 795 | }, 796 | passwordagain: { 797 | name: "Passwordagain", 798 | type: "match", 799 | match: "password", 800 | message: "Does not match the given password" 801 | }, 802 | positive: { 803 | name: "Positive", 804 | type: "shortcut", 805 | shortcut: "number,positivenumber" 806 | }, 807 | negative: { 808 | name: "Negative", 809 | type: "shortcut", 810 | shortcut: "number,negativenumber" 811 | }, 812 | number: { 813 | name: "Number", 814 | type: "regex", 815 | regex: "([+-]?\\\d+(\\\.\\\d*)?([eE][+-]?[0-9]+)?)?", 816 | message: "Must be a number" 817 | }, 818 | integer: { 819 | name: "Integer", 820 | type: "regex", 821 | regex: "[+-]?\\\d+", 822 | message: "No decimal places allowed" 823 | }, 824 | positivenumber: { 825 | name: "Positivenumber", 826 | type: "min", 827 | min: 0, 828 | message: "Must be a positive number" 829 | }, 830 | negativenumber: { 831 | name: "Negativenumber", 832 | type: "max", 833 | max: 0, 834 | message: "Must be a negative number" 835 | }, 836 | required: { 837 | name: "Required", 838 | type: "required", 839 | message: "This is required" 840 | }, 841 | checkone: { 842 | name: "Checkone", 843 | type: "minchecked", 844 | minchecked: 1, 845 | message: "Check at least one option" 846 | } 847 | } 848 | }; 849 | 850 | var formatValidatorName = function (name) { 851 | return name 852 | .toLowerCase() 853 | .replace( 854 | /(^|\s)([a-z])/g , 855 | function(m,p1,p2) { 856 | return p1+p2.toUpperCase(); 857 | } 858 | ) 859 | ; 860 | }; 861 | 862 | var getValue = function ($this) { 863 | // Extract the value we're talking about 864 | var value = $this.val(); 865 | var type = $this.attr("type"); 866 | if (type === "checkbox") { 867 | value = ($this.is(":checked") ? value : ""); 868 | } 869 | if (type === "radio") { 870 | value = ($('input[name="' + $this.attr("name") + '"]:checked').length > 0 ? value : ""); 871 | } 872 | return value; 873 | }; 874 | 875 | function regexFromString(inputstring) { 876 | return new RegExp("^" + inputstring + "$"); 877 | } 878 | 879 | /** 880 | * Thanks to Jason Bunting via StackOverflow.com 881 | * 882 | * http://stackoverflow.com/questions/359788/how-to-execute-a-javascript-function-when-i-have-its-name-as-a-string#answer-359910 883 | * Short link: http://tinyurl.com/executeFunctionByName 884 | **/ 885 | function executeFunctionByName(functionName, context /*, args*/) { 886 | var args = Array.prototype.slice.call(arguments).splice(2); 887 | var namespaces = functionName.split("."); 888 | var func = namespaces.pop(); 889 | for(var i = 0; i < namespaces.length; i++) { 890 | context = context[namespaces[i]]; 891 | } 892 | return context[func].apply(this, args); 893 | } 894 | 895 | $.fn.jqBootstrapValidation = function( method ) { 896 | 897 | if ( defaults.methods[method] ) { 898 | return defaults.methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); 899 | } else if ( typeof method === 'object' || ! method ) { 900 | return defaults.methods.init.apply( this, arguments ); 901 | } else { 902 | $.error( 'Method ' + method + ' does not exist on jQuery.jqBootstrapValidation' ); 903 | return null; 904 | } 905 | 906 | }; 907 | 908 | $.jqBootstrapValidation = function (options) { 909 | $(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this,arguments); 910 | }; 911 | 912 | })( jQuery ); 913 | -------------------------------------------------------------------------------- /src_meet_ym/images/screen00.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/screen00.jpg -------------------------------------------------------------------------------- /src_meet_ym/images/screen_a01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/screen_a01.png -------------------------------------------------------------------------------- /src_meet_ym/images/screen_a02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/screen_a02.png -------------------------------------------------------------------------------- /src_meet_ym/images/screen_a03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/screen_a03.png -------------------------------------------------------------------------------- /src_meet_ym/images/thesystem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/thesystem.png -------------------------------------------------------------------------------- /src_meet_ym/images/thesystem_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/thesystem_architecture.png -------------------------------------------------------------------------------- /src_meet_ym/images/welcome_page/applestore_qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/welcome_page/applestore_qrcode.png -------------------------------------------------------------------------------- /src_meet_ym/images/welcome_page/appstore.svg: -------------------------------------------------------------------------------- 1 | 2 | Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src_meet_ym/images/welcome_page/googleplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/welcome_page/googleplay.png -------------------------------------------------------------------------------- /src_meet_ym/images/welcome_page/googleplay_qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/welcome_page/googleplay_qrcode.png -------------------------------------------------------------------------------- /src_meet_ym/images/ym.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/ym.jpg -------------------------------------------------------------------------------- /src_meet_ym/images/ymlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuchunchen/BuildYourOwnConferenceSystem/ccd08eac5d85921e6fc9a313497774503c9d531c/src_meet_ym/images/ymlogo.png -------------------------------------------------------------------------------- /src_meet_ym/libs/qrcode.min.js: -------------------------------------------------------------------------------- 1 | var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); -------------------------------------------------------------------------------- /src_meet_ym/libs/scripts.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - Freelancer v6.0.0 (https://startbootstrap.com/themes/freelancer) 3 | * Copyright 2013-2020 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap-freelancer/blob/master/LICENSE) 5 | */ 6 | (function($) { 7 | "use strict"; // Start of use strict 8 | 9 | // Smooth scrolling using jQuery easing 10 | $('a.js-scroll-trigger[href*="#"]:not([href="#"])').click(function() { 11 | if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname) { 12 | var target = $(this.hash); 13 | target = target.length ? target : $('[name=' + this.hash.slice(1) + ']'); 14 | if (target.length) { 15 | $('html, body').animate({ 16 | scrollTop: (target.offset().top - 71) 17 | }, 1000, "easeInOutExpo"); 18 | return false; 19 | } 20 | } 21 | }); 22 | 23 | // Scroll to top button appear 24 | $(document).scroll(function() { 25 | var scrollDistance = $(this).scrollTop(); 26 | if (scrollDistance > 100) { 27 | $('.scroll-to-top').fadeIn(); 28 | } else { 29 | $('.scroll-to-top').fadeOut(); 30 | } 31 | }); 32 | 33 | // Closes responsive menu when a scroll trigger link is clicked 34 | $('.js-scroll-trigger').click(function() { 35 | $('.navbar-collapse').collapse('hide'); 36 | }); 37 | 38 | // Activate scrollspy to add active class to navbar items on scroll 39 | $('body').scrollspy({ 40 | target: '#mainNav', 41 | offset: 80 42 | }); 43 | 44 | // Collapse Navbar 45 | var navbarCollapse = function() { 46 | if ($("#mainNav").offset().top > 100) { 47 | $("#mainNav").addClass("navbar-shrink"); 48 | } else { 49 | $("#mainNav").removeClass("navbar-shrink"); 50 | } 51 | }; 52 | // Collapse now if page is not at top 53 | navbarCollapse(); 54 | // Collapse the navbar when page is scrolled 55 | $(window).scroll(navbarCollapse); 56 | 57 | // Floating label headings for the contact form 58 | $(function() { 59 | $("body").on("input propertychange", ".floating-label-form-group", function(e) { 60 | $(this).toggleClass("floating-label-form-group-with-value", !!$(e.target).val()); 61 | }).on("focus", ".floating-label-form-group", function() { 62 | $(this).addClass("floating-label-form-group-with-focus"); 63 | }).on("blur", ".floating-label-form-group", function() { 64 | $(this).removeClass("floating-label-form-group-with-focus"); 65 | }); 66 | }); 67 | 68 | })(jQuery); // End of use strict 69 | -------------------------------------------------------------------------------- /src_meet_ym/meet_ym.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 陽明大學視訊會議系統 10 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 154 | 155 | 156 | 157 | 158 | 184 | 185 |
186 |
187 | 188 | 189 | 190 |

191 | 陽明大學。愛家實驗室。Jitsi 192 |

193 | 194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 | 202 |
203 |
204 | 205 |
206 |
207 |
208 |

209 |
210 |
211 |
212 | 213 |

214 | 我要開會! 215 |

216 | 217 |
218 |
219 |

220 |

STEP1.

221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 | 229 | 231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |

STEP2.

239 |
240 |
241 |
242 |
243 | 輸入會議名稱後,請按這裏 245 |
246 |
247 | 248 |
249 |
250 |

STEP3.

251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |

STEP4.

263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 | 272 | 274 |
275 |
276 |
277 |
278 | 280 |
281 |
282 |
283 |
284 |
285 |
286 | (您可將下列訊息複製寄送給與會同仁) 287 |
288 |
289 |
290 |
291 |

292 |

293 |
294 |
295 |
296 |
297 | 298 | 299 | 300 | 301 | 302 |
303 |
304 | 305 |
306 |
307 |
308 | 309 |
310 |
311 |
312 | 313 |

314 | 開會去 315 |

316 | 317 |
318 |
319 |

手機桌機攏ㄟ通,我們提供多種使用者友善連線方法,您可以依照您的需求、環境與設備選擇最適合您的方式參與會議。(使用說明)

320 |
321 | 322 |
323 |
324 |
325 |

我想使用手機/平板開會

326 |
327 |
328 | 1. 請先安裝視訊會議用APP (掃描QR-Code) 329 |
330 |
331 |
332 |

蘋果iOS:
333 | 334 | 335 |

336 |

337 |

338 |
339 |
340 |

安卓Android:
341 | 342 | 343 | 344 |

345 |

346 | 347 |

348 |
349 |
350 |
351 | 2. 掃描會議管理者給您的QR-code開會去! 352 |
353 |
354 |
355 |
356 |

我想用桌機/筆記型電腦

357 |
358 |

您不需要另外安裝軟體,打開瀏覽器或直接點擊會議管理者給您的連結就可以開會去囉!

359 |

經過測試,我們發現使用Google Chrome瀏覽器參加視訊會議會有最好的使用者感受,如果使用其他瀏覽器有問題的話,建議您可以改用Google Chrome試試看。(使用說明在這邊)

360 |
361 |
362 |
363 |
364 |
365 | 366 |
367 |
368 | 369 |

370 | 關於陽明大學視訊會議系統 371 |

372 | 373 |
374 |
375 |
376 |
377 |
378 | 379 |
380 |
381 |

382 | 「陽明大學視訊會議系統」為醫學系陳育群副教授愛家實驗室團隊為陽明大學客製化開發,以自由開源的跨平台語音、視訊會議和即時通訊應用平台JITSI meet為基礎,架設安全且隱私的視訊會議系統。(參考使用說明)

383 |

384 |

自由、安全、隱私是此視訊系統設計最重要考量,我們不會收集任何資料,除了Jitis meet系統資訊安全設計(了解更多)外,我們更在陽明視訊會議系統中採取下列設計確保與會者個人隱私:

385 |
    386 |
  • 主持人身份驗證,可設置會議室密碼,確保會議安全性。
  • 387 |
  • 不須額外註冊,減少與會者個資外洩風險。
  • 388 |
  • 所有人離開會議後,會議室自動關閉,連結及密碼均將被重置,維護會議的單一性。
  • 389 | 390 |
391 |
392 |
393 |
394 |
395 | 396 |
397 |
398 | 399 |

400 | 關於愛家實驗室 401 |

402 | 403 |
404 |
405 |
406 |
407 |
408 | 409 |
410 |
411 |

412 | 「愛家實驗室」是由陽明大學醫學系家庭醫學科陳育群副教授於2016年所創立, 413 | 秉持著「科技助人」的理念,透過移動科技及人工智慧,將最好的醫療服務帶到每個人家中, 414 | 為大家解決醫療照護上大大小小問題,共同創造智慧新體驗。(了解更多) 415 |

416 |
417 |
418 |
419 |
420 | 421 | 424 | 425 |
426 | 428 |
429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | -------------------------------------------------------------------------------- /src_meet_ym/meet_ym_browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 陽明大學視訊會議系統() 10 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 156 | 157 | 158 | 159 | 160 | 186 | 187 |
188 |
189 | 190 |

191 | 陽明大學。愛家實驗室 192 |

193 | 194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 | 202 |
203 |
204 | 205 |
206 |
207 |
208 |

209 |
210 |
211 |
212 | 213 |

214 | 我要開會! 215 |

216 | 217 |
218 |
219 |

STEP1.下載APP

220 |
221 |
222 | 蘋果iOS: 223 | 224 |
225 |
226 | 安卓Android: 227 | 228 |
229 |
230 |
231 |
232 |

STEP2.

233 |
234 |
235 |
236 |
237 | (只要設定一次即可)依照下面影片從螢幕左上角 > Settings > Server URL 改成 https://jitsiym.aigia.ai。 238 | 241 |
242 |
243 |
244 |
245 |

STEP3.

246 |
247 |
248 |
249 |
250 | 1.輸入此次會議名稱(建議可用會議名稱字尾加上日期,如:medfaculy_20200415) 251 | 252 |
253 |
254 | 2.系統提示您是此次會議管理者,按下 OK 255 | 256 |
257 |
258 | 3.請輸入會議管理者帳號密碼。沒有此帳號密碼無法開啟會議。 259 | 260 |
261 |
262 |
263 |
264 |
265 |

STEP4.

266 | 請告知與會者您剛剛設定的「會議名稱」即可開始會議。 267 |
268 |
269 |
270 |
271 |

STEP5.

272 | 小提醒,進入會議後記得設定開會密碼保護隱私唷! 273 |
274 |
275 | 276 |
277 | 278 |
279 |
280 | 281 |
282 |
283 |
284 | 285 |
286 |
287 |
288 | 289 |

290 | 參加會議 291 |

292 | 293 |
294 |
295 |

參加會議很簡單。

296 |
297 |
298 |
299 |
300 |

STEP1.先安裝APP

301 |
302 |
303 | 蘋果iOS: 304 | 305 |
306 |
307 | 安卓Android: 308 | 309 |
310 |
311 |

STEP2.

請點擊會議管理者給您的連結或開啟相機掃QR-code登入即可。 312 |
313 |
314 | 315 | 316 |
317 |
318 | 319 |
320 |
321 | 322 |

323 | 關於陽明大學視訊會議系統 324 |

325 | 326 |
327 |
328 |
329 |
330 |
331 | 332 |
333 |
334 | 「陽明大學視訊會議系統」為醫學系陳育群副教授愛家實驗室團隊為陽明大學客製化開發,以自由開源的跨平台語音、視訊會議和即時通訊應用平台JITSI meet為基礎,架設安全且隱私的視訊會議系統。(參考使用說明)

335 |

336 |

自由、安全、隱私是這視訊系統設計最重要考量,我們不會收集任何資料,除了Jitis meet系統資訊安全設計(了解更多)外,我們更在陽明視訊會議系統中採取下列設計確保與會者個人隱私:

337 |
    338 |
  • 主持人身份驗證,可設置會議室密碼,確保會議安全性。
  • 339 |
  • 不須額外註冊,減少與會者個資外洩風險。
  • 340 |
  • 所有人離開會議後,會議室自動關閉,連結及密碼均將被重置,維護會議的單一性。
  • 341 |
342 |
343 |
344 |
345 |
346 | 347 |
348 |
349 | 350 |

351 | 關於愛家實驗室 352 |

353 | 354 |
355 |
356 |
357 |
358 |
359 | 360 |
361 |
362 |

363 | 「愛家實驗室」是由陽明大學醫學系家庭醫學科陳育群副教授於2016年所創立, 364 | 秉持著「科技助人」的理念,透過移動科技及人工智慧,將最好的醫療服務帶到每個人家中, 365 | 為大家解決醫療照護上大大小小問題,共同創造智慧新體驗。(了解更多) 366 |

367 |
368 |
369 |
370 |
371 | 372 | 375 | 376 |
377 | 379 |
380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | --------------------------------------------------------------------------------