├── .gitattributes ├── .gitignore ├── Instructions.md ├── MarkdownEditorForWord.sln ├── MarkdownEditorForWord ├── MarkdownEditorForWord.xproj ├── Properties │ └── launchSettings.json ├── Startup.cs ├── config │ ├── helpers.js │ ├── webpack.common.js │ ├── webpack.dev.js │ └── webpack.prod.js ├── github-auth.js ├── package.json ├── project.json ├── project.lock.json ├── src │ ├── app.ts │ ├── app │ │ ├── app.component.ts │ │ ├── app.routes.ts │ │ ├── components.ts │ │ ├── file │ │ │ ├── create │ │ │ │ ├── file-create.component.html │ │ │ │ ├── file-create.component.scss │ │ │ │ └── file-create.component.ts │ │ │ ├── detail │ │ │ │ ├── file-detail.component.html │ │ │ │ ├── file-detail.component.scss │ │ │ │ └── file-detail.component.ts │ │ │ ├── file.routes.ts │ │ │ ├── index.ts │ │ │ ├── list │ │ │ │ ├── file-list.component.html │ │ │ │ ├── file-list.component.scss │ │ │ │ └── file-list.component.ts │ │ │ └── tree │ │ │ │ ├── file-tree.component.html │ │ │ │ ├── file-tree.component.scss │ │ │ │ └── file-tree.component.ts │ │ ├── login │ │ │ ├── index.ts │ │ │ ├── login.component.html │ │ │ ├── login.component.scss │ │ │ └── login.component.ts │ │ ├── repo │ │ │ ├── index.ts │ │ │ ├── repo.component.html │ │ │ ├── repo.component.scss │ │ │ └── repo.component.ts │ │ └── shared │ │ │ ├── components │ │ │ ├── base.component.ts │ │ │ ├── breadcrumb │ │ │ │ ├── breadcrumb.component.html │ │ │ │ ├── breadcrumb.component.scss │ │ │ │ └── breadcrumb.component.ts │ │ │ ├── hamburger │ │ │ │ ├── hamburger.component.html │ │ │ │ ├── hamburger.component.scss │ │ │ │ └── hamburger.component.ts │ │ │ ├── index.ts │ │ │ └── notification │ │ │ │ ├── async-view.component.html │ │ │ │ ├── async-view.component.scss │ │ │ │ ├── async-view.component.ts │ │ │ │ ├── message-bar.component.scss │ │ │ │ ├── message-bar.component.ts │ │ │ │ ├── toast.component.scss │ │ │ │ └── toast.component.ts │ │ │ ├── helpers │ │ │ ├── exception.helper.ts │ │ │ ├── index.ts │ │ │ ├── request.helper.ts │ │ │ └── utilities.ts │ │ │ ├── pipes │ │ │ ├── index.ts │ │ │ ├── md-filter.pipe.ts │ │ │ └── safenames.pipe.ts │ │ │ └── services │ │ │ ├── auth.guard.ts │ │ │ ├── favorites.service.ts │ │ │ ├── github.service.ts │ │ │ ├── index.ts │ │ │ ├── markdown.service.ts │ │ │ ├── mediator.service.ts │ │ │ ├── models.ts │ │ │ ├── notification.service.ts │ │ │ └── word.service.ts │ ├── assets │ │ ├── images │ │ │ ├── black_brandbar.png │ │ │ ├── logo.png │ │ │ ├── logo_16.png │ │ │ ├── logo_32.png │ │ │ ├── logo_64.png │ │ │ ├── logo_80.png │ │ │ └── white_brandbar.png │ │ ├── scripts │ │ │ └── stringview.js │ │ ├── styles │ │ │ ├── globals.scss │ │ │ ├── mixins.scss │ │ │ ├── mixins │ │ │ │ ├── background.fill.scss │ │ │ │ ├── ellipsis.scss │ │ │ │ ├── flex.scss │ │ │ │ ├── focus.states.scss │ │ │ │ ├── keyframes.scss │ │ │ │ └── scroll.scss │ │ │ ├── spinner.scss │ │ │ └── theme.scss │ │ └── templates │ │ │ ├── contributing.md │ │ │ ├── mit-license.md │ │ │ ├── object-definition.md │ │ │ ├── readme.md │ │ │ └── simple-file.md │ ├── index.html │ ├── polyfills.ts │ └── vendor.ts ├── tsconfig.json ├── tslint.json ├── typings.json └── webpack.config.js ├── MarkdownEditorForWordAddin ├── MarkdownEditorForWordAddin.csproj └── MarkdownEditorForWordAddin │ ├── MarkdownEditorForWordAddinManifest.xml │ └── SharePointProjectItem.spdata ├── README.md └── Word-Add-in-Markdown-Editor.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/osx,windows 2 | 3 | ### OSX ### 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | 31 | ### Windows ### 32 | # Windows image file caches 33 | Thumbs.db 34 | ehthumbs.db 35 | 36 | # Folder config file 37 | Desktop.ini 38 | 39 | # Recycle Bin used on file shares 40 | $RECYCLE.BIN/ 41 | 42 | # Windows Installer files 43 | *.cab 44 | *.msi 45 | *.msm 46 | *.msp 47 | 48 | # Windows shortcuts 49 | *.lnk 50 | 51 | # Ignore the following folders 52 | .vs/** 53 | .idea/** 54 | *.log.* 55 | 56 | /MarkdownEditorForWord/bin/** 57 | /MarkdownEditorForWord/obj/** 58 | /MarkdownEditorForWord/node_modules/** 59 | /MarkdownEditorForWord/typings/** 60 | /MarkdownEditorForWord/dist/** 61 | 62 | /MarkdownEditorForWordAddin/bin/** 63 | /MarkdownEditorForWordAddin/obj/** 64 | **/*.user 65 | /MarkdownEditorForWordAddin/pkgobj/Debug/AppManifest.xml 66 | -------------------------------------------------------------------------------- /Instructions.md: -------------------------------------------------------------------------------- 1 | # Word Add-in Markdown Editor 2 | 3 | Learn how you use the the add-in after you've installed it. 4 | 5 | ## Table of Contents 6 | * [Change History](#change-history) 7 | * [Overview](#overview) 8 | * [Add-in capabilities](#capabilities) 9 | * [Instructions and known issues](#instructions) 10 | * [Questions and comments](#questions-and-comments) 11 | * [Additional resources](#additional-resources) 12 | 13 | ## Change History 14 | 15 | November 2016: 16 | * Initial sample version. 17 | 18 | 19 | ## Overview 20 | The Word Add-in Markdown Editor add-in is designed to make working with markdown file, tables, adding images, etc, easier. You can use Word to edit the document . When you’re ready to sync the changes to GitHub, the add-in converts the document to markdown and uploads it to GitHub. You don’t have to format it to markdown; the add-in does it for you. 21 | 22 | > **Note:** For instructions on how to install and run the add-in, see the [ReadMe] (ReadMe.md). 23 | 24 | 25 | ## Add-in capabilities 26 | The add-in in has the following capabilities: 27 | 28 | 1. Sign in to GitHub from within Word. 29 | 2. Browse your personal and organizational repositories. 30 | 3. Mark favorite repositories so you can quickly access them in the future. 31 | 4. Browse the markdown files in your repos, drilling down into folders. 32 | 5. Create new markdown files in the folder structure you want in a repositories. 33 | 6. Ability to create object definition, sample readme, conceptual documents based on predefined templates, as well as blank documents. 34 | 7. Edit markdown files. Support includes text, lists, tables, pictures, hyperlinks, etc. 35 | 8. Sync to GitHub. 36 | 9. Pull the latest from GitHub. 37 | 10. Get notification in case of a merge conflict. 38 | 39 | 40 | ## Instructions and known issues 41 | Try out the add-in and use it to do the usual things you’d do to create your typical markdown files. There are, however, a few things to point out: 42 | 43 | 44 | ### Styles 45 | GitHub only supports HTML styles. Therefore, instead of using Title, Subtitle, etc., use Heading1, Heading2, etc. in your Word documents. 46 | 47 | ### Bulleted and numbered lists 48 | 49 | - Although you can edit lists that already exist in a document, if you want to start a new list, you cannot use the lists UI in Word (for now). To work around this, the add-in has two buttons – **Insert Bullets** and **Insert Numbering**, on the **File Details** page. Use them when you want to start a new list and once the list is inserted, you can edit them. 50 | - Only two levels of nesting are supported in lists. In addition, for now, each list inserted this way needs to be followed by a paragraph (the paragraph can be empty). 51 | 52 | ### Code 53 | If you want to insert new code fragments and style them as code, you can do so using the **HTML Code** style in Word. However, this may not be as straightforward as it seems because Word only shows the recommended styles by default in the UI. To get the **HTML Code** style to show up in the UI, you need to follow these steps once. 54 | 55 | 1. At the bottom of the style pane, click on the **options...** hyperlink to display the **Style Pane Options** dialog. 56 | 2. In the **Select Styles to Show** drop down, select **All Styles**. Click **OK**. 57 | 58 | You should now see all the styles in the **Styles** box. When you want to format a selection as code, simply apply the **HTML Code** style to it. 59 | 60 | 61 | ### Favorite repositories 62 | For repositories you access often, you can add them as favorites. You can then easily access them in the future from the hamburger menu which is displayed at launch, skipping the repositories screen altogether. 63 | 64 | > **Note:** Currently there is no indicator that made a repository a favorite on the repositories screen. 65 | 66 | ### Images 67 | 68 | - The add-in assumes images are stored in the **Images** folder under the root of the repository. 69 | - For images inserted into Word, image names are auto-generated. 70 | - There is currently no image resizing API. Therefore, sometimes, you might notice a large image imported into Word. Simply resize the image again. Currently there isn't any workaround for this. 71 | - Only pictures can be inserted into the document. If you've any other embedded object, such as a chart or a shape, the API call will fail. Therefore, make sure not to have anything else other than pictures in your document. 72 | 73 | ### Commit messages 74 | Commit messages are auto-generated now and not user provided. 75 | 76 | ### Underlines 77 | There’s no underline support in markdown, so there’s no support for underlines. 78 | 79 | ### Digital pens 80 | Digital pens aren't supported. If you write on your document with a digital pen, the API calls will fail. So do not write on your documents with digital pens. 81 | 82 | ### Merge conflicts 83 | Currently the add-in doesn't support merge conflicts. You must manage merge conflicts yourself. 84 | 85 | ## Questions and comments 86 | 87 | We'd love to get your feedback about this sample. You can send your feedback to us in the *Issues* section of this repository. 88 | 89 | Questions about Microsoft Office 365 development in general should be posted to [Stack Overflow](http://stackoverflow.com/questions/tagged/office-js+API). Make sure that your questions are tagged with [office-js] and [API]. 90 | 91 | ## Additional resources 92 | 93 | * [Office add-in documentation](https://msdn.microsoft.com/en-us/library/office/jj220060.aspx) 94 | * [Office Dev Center](http://dev.office.com/) 95 | * [Office 365 APIs starter projects and code samples](http://msdn.microsoft.com/en-us/office/office365/howto/starter-projects-and-code-samples) 96 | 97 | ## Copyright 98 | Copyright (c) 2016 Microsoft Corporation. All rights reserved. 99 | -------------------------------------------------------------------------------- /MarkdownEditorForWord.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5C859053-B2B7-435B-B2C6-07C63E2077CF}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitattributes = .gitattributes 9 | .gitignore = .gitignore 10 | EndProjectSection 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {F180F779-7692-4635-9C85-84D2C5A8F9CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {F180F779-7692-4635-9C85-84D2C5A8F9CA}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {F180F779-7692-4635-9C85-84D2C5A8F9CA}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 21 | {F180F779-7692-4635-9C85-84D2C5A8F9CA}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {F180F779-7692-4635-9C85-84D2C5A8F9CA}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {F180F779-7692-4635-9C85-84D2C5A8F9CA}.Release|Any CPU.Deploy.0 = Release|Any CPU 24 | {CB0D80A7-C3E5-4859-9BA8-4AD27872BF53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {CB0D80A7-C3E5-4859-9BA8-4AD27872BF53}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/MarkdownEditorForWord.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | cb0d80a7-c3e5-4859-9ba8-4ad27872bf53 10 | WordToGitHub 11 | ..\..\artifacts\obj\$(MSBuildProjectName) 12 | .\bin\ 13 | 14 | 15 | 2.0 16 | True 17 | 15.0 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52551/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "node": { 12 | "executablePath": "C:/Program Files/nodejs/npm.cmd", 13 | "commandLineArgs": "start", 14 | "launchUrl": "http://localhost:3000/project_readme.html", 15 | "environmentVariables": { 16 | "Hosting:Environment": "Development" 17 | } 18 | }, 19 | "IIS Express": { 20 | "commandName": "IISExpress", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "Hosting:Environment": "Development" 24 | } 25 | }, 26 | "web": { 27 | "commandName": "web", 28 | "environmentVariables": { 29 | "Hosting:Environment": "Development" 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.FileProviders; 10 | using System.IO; 11 | using Microsoft.Extensions.Configuration; 12 | using Microsoft.AspNetCore.Hosting.Internal; 13 | 14 | namespace WordToGitHub 15 | { 16 | public class Startup 17 | { 18 | // This method gets called by the runtime. Use this method to add services to the container. 19 | // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddDirectoryBrowser(); 23 | } 24 | 25 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 26 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 27 | { 28 | app.UseStaticFiles(); 29 | app.UseDirectoryBrowser(); 30 | app.UseFileServer(); 31 | 32 | 33 | // this will serve up node_modules 34 | var provider = new PhysicalFileProvider( 35 | Path.Combine(env.ContentRootPath, "node_modules") 36 | ); 37 | 38 | var options = new FileServerOptions(); 39 | options.RequestPath = "/node_modules"; 40 | options.StaticFileOptions.FileProvider = provider; 41 | options.EnableDirectoryBrowsing = true; 42 | app.UseFileServer(options); 43 | } 44 | 45 | public static void Main(string[] args) 46 | { 47 | var host = new WebHostBuilder() 48 | .UseKestrel() 49 | .UseContentRoot(Directory.GetCurrentDirectory()) 50 | .UseIISIntegration() 51 | .UseStartup() 52 | .Build(); 53 | 54 | host.Run(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/config/helpers.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | // Helper functions 4 | var ROOT = path.resolve(__dirname, '..'); 5 | 6 | function node_modules(params) { 7 | return path.resolve(__dirname, 'node_modules/' + params); 8 | } 9 | 10 | function hasProcessFlag(flag) { 11 | return process.argv.join('').indexOf(flag) > -1; 12 | } 13 | 14 | function isWebpackDevServer() { 15 | return process.argv[1] && !! (/webpack-dev-server$/.exec(process.argv[1])); 16 | } 17 | 18 | function root(args) { 19 | args = Array.prototype.slice.call(arguments, 0); 20 | return path.join.apply(path, [ROOT].concat(args)); 21 | } 22 | 23 | function checkNodeImport(context, request, cb) { 24 | if (!path.isAbsolute(request) && request.charAt(0) !== '.') { 25 | cb(null, 'commonjs ' + request); return; 26 | } 27 | cb(); 28 | } 29 | 30 | exports.hasProcessFlag = hasProcessFlag; 31 | exports.isWebpackDevServer = isWebpackDevServer; 32 | exports.root = root; 33 | exports.checkNodeImport = checkNodeImport; 34 | exports.node_modules = node_modules; -------------------------------------------------------------------------------- /MarkdownEditorForWord/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var helpers = require('./helpers'); 3 | 4 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 7 | var autoprefixer = require('autoprefixer'); 8 | var perfectionist = require('perfectionist'); 9 | 10 | module.exports = { 11 | entry: { 12 | 'polyfills': './src/polyfills.ts', 13 | 'vendor': './src/vendor.ts', 14 | 'app': './src/app.ts' 15 | }, 16 | 17 | resolve: { 18 | extensions: ['', '.js', '.ts'] 19 | }, 20 | 21 | module: { 22 | loaders: [ 23 | { 24 | test: /\.ts$/, 25 | loaders: ['awesome-typescript-loader', 'angular2-template-loader'], 26 | exclude: /node_modules/ 27 | }, 28 | { 29 | test: /\.html$/, 30 | loader: 'html' 31 | }, 32 | { 33 | test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/, 34 | loader: 'file?name=assets/[name].[ext]' 35 | }, 36 | { 37 | test: /\.scss$/, 38 | loader: ExtractTextPlugin.extract('style-loader', 'css!postcss?sourceMap=inline!sass'), 39 | exclude: /node_modules/ 40 | } 41 | ], 42 | 43 | preLoaders: [ 44 | { 45 | test: /\.js$/, 46 | loader: "source-map-loader" 47 | } 48 | ] 49 | }, 50 | 51 | postcss: function () { 52 | return [autoprefixer({ browsers: ['Safari >= 8', 'last 2 versions'] }), perfectionist]; 53 | }, 54 | 55 | externals: { 56 | 'underscore': '_', 57 | 'jquery': '$', 58 | 'to-markdown': 'toMarkdown' 59 | }, 60 | 61 | plugins: [ 62 | new webpack.optimize.CommonsChunkPlugin({ 63 | name: ['polyfills', 'vendor', 'app'].reverse() 64 | }), 65 | 66 | new HtmlWebpackPlugin({ 67 | template: 'src/index.html', 68 | title: 'Markdown Editor for Word', 69 | filename: 'index.html' 70 | }), 71 | 72 | new CopyWebpackPlugin([ 73 | { 74 | from: './src/assets', 75 | to: 'assets', 76 | } 77 | ]) 78 | ] 79 | }; -------------------------------------------------------------------------------- /MarkdownEditorForWord/config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var webpackMerge = require('webpack-merge'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var commonConfig = require('./webpack.common.js'); 5 | var helpers = require('./helpers'); 6 | 7 | module.exports = webpackMerge(commonConfig, { 8 | devtool: 'source-map', 9 | 10 | output: { 11 | path: helpers.root('dist'), 12 | publicPath: 'https://localhost:3000/', 13 | filename: '[name].[hash].js', 14 | chunkFilename: '[id].[hash].chunk.js' 15 | }, 16 | 17 | plugins: [ 18 | new ExtractTextPlugin('[name].css') 19 | ], 20 | 21 | devServer: { 22 | https: true, 23 | historyApiFallback: true, 24 | stats: 'minimal', 25 | watchOptions: { 26 | aggregateTimeout: 300, 27 | poll: 1000 28 | }, 29 | outputPath: helpers.root('dist') 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var webpackMerge = require('webpack-merge'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var commonConfig = require('./webpack.common.js'); 5 | var helpers = require('./helpers'); 6 | 7 | const ENV = process.env.NODE_ENV = process.env.ENV = 'production'; 8 | 9 | module.exports = webpackMerge(commonConfig, { 10 | devtool: 'source-map', 11 | 12 | output: { 13 | path: helpers.root('dist'), 14 | filename: '[name].js', 15 | chunkFilename: '[id].chunk.js' 16 | }, 17 | 18 | htmlLoader: { 19 | minimize: false // workaround for ng2 20 | }, 21 | 22 | plugins: [ 23 | new webpack.NoErrorsPlugin(), 24 | new webpack.optimize.DedupePlugin(), 25 | new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618 26 | mangle: { 27 | keep_fnames: true 28 | } 29 | }), 30 | new ExtractTextPlugin('[name].css'), 31 | new webpack.DefinePlugin({ 32 | 'process.env': { 33 | 'ENV': JSON.stringify(ENV) 34 | } 35 | }) 36 | ] 37 | }); 38 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/github-auth.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | 3 | module.exports = function (context, data) { 4 | context.log(data); 5 | if ('code' in data) { 6 | context.log('code: ' + data.code); 7 | request.post({ 8 | url: 'https://github.com/login/oauth/access_token', 9 | json: { 10 | client_id: '', 11 | client_secret: '', 12 | redirect_uri: 'https://word-to-github.azurewebsites.net', 13 | code: data.code 14 | } 15 | }, function (err, httpResponse, body) { 16 | if (err) { 17 | context.log('error: ' + err); 18 | context.res = { 19 | body: { 20 | status: 500, 21 | error: err 22 | } 23 | } 24 | } 25 | else { 26 | context.log(body); 27 | context.res = { body: body }; 28 | } 29 | 30 | context.done(); 31 | }); 32 | } 33 | else { 34 | context.res = { 35 | status: 400, 36 | body: { error: 'Please pass the GitHub code in the input object' } 37 | }; 38 | 39 | context.done(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-editor-for-word", 3 | "description": "Author your markdown files and push your changes to GitHub directly from Word - your favourite document editor.", 4 | "repository": "", 5 | "version": "1.0.0", 6 | "scripts": { 7 | "start": "webpack-dev-server --inline --progress --compress --port 3000 --display-error-details --open", 8 | "build": "rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail --colors --display-modules", 9 | "postinstall": "typings install" 10 | }, 11 | "license": "MIT", 12 | "dependencies": { 13 | "@angular/common": "^2.1.1", 14 | "@angular/compiler": "^2.1.1", 15 | "@angular/core": "^2.1.1", 16 | "@angular/forms": "^2.1.1", 17 | "@angular/http": "^2.1.1", 18 | "@angular/platform-browser": "^2.1.1", 19 | "@angular/platform-browser-dynamic": "^2.1.1", 20 | "@angular/router": "^3.1.1", 21 | "rxjs": "5.0.0-beta.12", 22 | "core-js": "^2.4.1", 23 | "zone.js": "^0.6.26", 24 | "@microsoft/office-js-helpers": "^0.1.13", 25 | "jquery": "^3.1.1", 26 | "office-ui-fabric": "^2.6.2", 27 | "marked": "^0.3.6", 28 | "to-markdown": "^3.0.2", 29 | "underscore": "^1.8.3" 30 | }, 31 | "devDependencies": { 32 | "perfectionist": "^2.3.1", 33 | "node-sass": "^3.10.1", 34 | "precss": "^1.4.0", 35 | "autoprefixer": "^6.5.1", 36 | "angular2-template-loader": "^0.5.0", 37 | "css-loader": "^0.25.0", 38 | "postcss-loader": "^1.0.0", 39 | "sass-loader": "^4.0.2", 40 | "extract-text-webpack-plugin": "^1.0.1", 41 | "source-map-loader": "^0.1.5", 42 | "file-loader": "^0.9.0", 43 | "html-loader": "^0.4.4", 44 | "html-webpack-plugin": "^2.22.0", 45 | "null-loader": "^0.1.1", 46 | "phantomjs-prebuilt": "^2.1.13", 47 | "raw-loader": "^0.5.1", 48 | "rimraf": "^2.5.4", 49 | "style-loader": "^0.13.1", 50 | "typescript": "^2.0.3", 51 | "typings": "^1.4.0", 52 | "webpack": "^1.13.2", 53 | "webpack-dev-server": "^1.16.2", 54 | "webpack-merge": "^0.15.0", 55 | "copy-webpack-plugin": "^3.0.1", 56 | "awesome-typescript-loader": "2.2.1" 57 | } 58 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "markdown-editor-for-word", 3 | "copyright": "Microsoft 2016", 4 | "description": "Author your markdown files and push your changes to GitHub directly from Word - your favourite document editor.", 5 | "language": "en", 6 | "dependencies": { 7 | "Microsoft.NETCore.App": { 8 | "version": "1.0.0-rc2-3002702", 9 | "type": "platform" 10 | }, 11 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final", 12 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final", 13 | "Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-final", 14 | "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final", 15 | "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final", 16 | "Nito.AsyncEx": "3.0.1" 17 | }, 18 | 19 | "tools": { 20 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": { 21 | "version": "1.0.0-preview1-final", 22 | "imports": "portable-net45+win8+dnxcore50" 23 | } 24 | }, 25 | 26 | "frameworks": { 27 | "netcoreapp1.0": { 28 | "imports": [ 29 | "dotnet5.6", 30 | "dnxcore50", 31 | "portable-net45+win8" 32 | ] 33 | } 34 | }, 35 | 36 | "buildOptions": { 37 | "emitEntryPoint": true, 38 | "preserveCompilationContext": true 39 | }, 40 | 41 | "runtimeOptions": { 42 | "gcServer": true 43 | }, 44 | 45 | "publishOptions": { 46 | "include": [ 47 | "wwwroot", 48 | "web.config" 49 | ] 50 | }, 51 | 52 | "scripts": { 53 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { ErrorHandler, NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | import { HttpModule } from '@angular/http'; 6 | import { Authenticator } from '@microsoft/office-js-helpers'; 7 | 8 | import { BASE_ROUTES, FILE_ROUTES } from "./app/app.routes"; 9 | import { AppComponent, COMPONENTS } from "./app/components"; 10 | import { SERVICES } from "./app/shared/services"; 11 | import { PIPES } from "./app/shared/pipes"; 12 | import { HELPERS } from "./app/shared/helpers"; 13 | 14 | require('./assets/styles/spinner.scss'); 15 | require('./assets/styles/globals.scss'); 16 | 17 | @NgModule({ 18 | imports: [BrowserModule, HttpModule, FormsModule, BASE_ROUTES, FILE_ROUTES], 19 | declarations: [...COMPONENTS, ...PIPES], 20 | bootstrap: [AppComponent], 21 | providers: [...SERVICES, ...HELPERS] 22 | }) 23 | export class AppModule { 24 | } 25 | 26 | Office.initialize = reason => { } 27 | if (!Authenticator.isAuthDialog()) { 28 | platformBrowserDynamic().bootstrapModule(AppModule); 29 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router, NavigationStart, NavigationEnd } from '@angular/router'; 3 | import { Utilities } from './shared/helpers'; 4 | 5 | @Component({ 6 | selector: 'app', 7 | template: 8 | ` 9 |
10 | 11 | 12 | 13 |
14 |
` 15 | }) 16 | 17 | export class AppComponent { 18 | constructor(router: Router) { 19 | router.events.subscribe(next => { 20 | if (next instanceof NavigationStart) { 21 | if (Utilities.isEmpty(next.url)) return; 22 | var name = next.url.split('/')[1]; 23 | appInsights.startTrackPage(name); 24 | } 25 | else if (next instanceof NavigationEnd) { 26 | var url = (next as NavigationEnd).url 27 | if (Utilities.isEmpty(url)) return; 28 | var name = url.split('/')[1]; 29 | appInsights.stopTrackPage( 30 | name, 31 | url 32 | ); 33 | } 34 | }); 35 | } 36 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { LoginComponent, RepoComponent, File } from "./components"; 4 | import { AuthGuard } from './shared/services'; 5 | 6 | const BaseRoutes: Routes = [ 7 | { 8 | path: 'login', 9 | component: LoginComponent 10 | }, 11 | { 12 | path: '', 13 | component: RepoComponent, 14 | canActivate: [AuthGuard] 15 | }, 16 | { 17 | path: ':org', 18 | component: RepoComponent, 19 | canActivate: [AuthGuard] 20 | } 21 | ]; 22 | 23 | export const BASE_ROUTES = RouterModule.forRoot(BaseRoutes, { 24 | useHash: true 25 | }); 26 | 27 | export const FILE_ROUTES = File.FILE_ROUTES; -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/components.ts: -------------------------------------------------------------------------------- 1 | import { AppComponent } from './app.component'; 2 | import { LoginComponent } from './login'; 3 | import { RepoComponent } from './repo'; 4 | import * as File from './file'; 5 | import * as Shared from './shared/components'; 6 | 7 | export { AppComponent, LoginComponent, RepoComponent, File, Shared }; 8 | export const COMPONENTS = [ 9 | LoginComponent, 10 | RepoComponent, 11 | File.FileListComponent, 12 | File.FileTreeComponent, 13 | File.FileDetailComponent, 14 | File.FileCreateComponent, 15 | Shared.AsyncViewComponent, 16 | Shared.BreadcrumbComponent, 17 | Shared.HamburgerComponent, 18 | Shared.MessageBarComponent, 19 | Shared.ToastComponent, 20 | AppComponent 21 | ] -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/create/file-create.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
    3 |
  • 4 |
  • 5 |

    New File

    6 |
  • 7 |
8 |
9 | 10 |
11 |
12 |
13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 |

Your file will be created at:

29 |
@{{selectedOrg}}/{{selectedRepoName}}/{{selectedBranch}}/{{selectedPath}}/{{selectedFolder|safenames:true}}/{{selectedFile|safenames:true}}.md
30 |
31 | 32 |
33 |

Templates

34 |
35 | 36 |
37 |
    38 |
  • 39 |

    {{template.title}}

    40 |

    {{template.description}}

    41 |
  • 42 |
43 |
44 | 45 |
46 | 47 |
48 |
49 |
-------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/create/file-create.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixins.scss'; 2 | @import '../../../assets/styles/theme.scss'; 3 | 4 | .file-create-component { 5 | &__separator { 6 | margin: 0 auto; 7 | width: calc(100% - 30px); 8 | border: none; 9 | border-top: 1px solid $background-light; 10 | } 11 | 12 | &__header { 13 | background-color: $background-primary; 14 | } 15 | 16 | &__form { 17 | background-color: $background-primary; 18 | flex: 1 1 100%; 19 | } 20 | } 21 | 22 | .file-create-command-bar { 23 | @include inline-flex(); 24 | list-style-type: none; 25 | width: 100%; 26 | margin-bottom: $default-spacing; 27 | 28 | &__command { 29 | padding: (40px - $default-spacing)/2 $default-spacing; 30 | @include focus-states($accent-background-primary, $accent-foreground-primary); 31 | 32 | i { 33 | position: relative; 34 | top: 2px; 35 | color: $foreground-white; 36 | } 37 | 38 | &:hover i { 39 | color: $accent-foreground-primary; 40 | } 41 | } 42 | 43 | &__text { 44 | padding: (40px - $default-spacing)/2 $default-spacing; 45 | flex: 1 1 0px; 46 | text-align: center; 47 | @include ellipsis(); 48 | 49 | h1 { 50 | color: $foreground-white; 51 | } 52 | } 53 | } 54 | 55 | .file-create-form { 56 | @include flex(); 57 | height: 100%; 58 | 59 | &__section { 60 | margin-bottom: $default-spacing; 61 | padding: 0 $default-spacing; 62 | 63 | &--menu { 64 | padding: 0; 65 | position: relative; 66 | @include vScroll(); 67 | } 68 | 69 | &--header { 70 | padding-bottom: 20px; 71 | margin-bottom: 0; 72 | border-bottom: solid 1px rgba($foreground-light, 0.5); 73 | } 74 | } 75 | 76 | &__label { 77 | width: 100%; 78 | color: $foreground-lighter; 79 | } 80 | 81 | &__text { 82 | width: calc(100% - 15px); 83 | height: calc(32px - 15px); 84 | border: solid 1px $foreground-primary; 85 | background: $foreground-primary; 86 | color: $foreground-white; 87 | font-size: 10pt; 88 | margin-top: $default-spacing / 2; 89 | padding: $default-spacing / 2; 90 | 91 | &:hover { 92 | background-color: $foreground-primary; 93 | } 94 | 95 | &:focus { 96 | background-color: $foreground-primary; 97 | border-color: $accent-background-primary; 98 | } 99 | } 100 | 101 | &__check { 102 | padding-right: $default-spacing / 2; 103 | } 104 | 105 | &__path-section { 106 | flex: 1 0 auto; 107 | } 108 | 109 | &__path { 110 | color: $foreground-lightest; 111 | white-space: pre-wrap; 112 | word-break: break-all; 113 | margin-top: $default-spacing/2; 114 | 115 | &--folder { 116 | color: #48a0fc; 117 | } 118 | 119 | &--file { 120 | color: #3cc7ae; 121 | } 122 | } 123 | 124 | &__templates { 125 | list-style-type: none; 126 | flex: 1 0 auto; 127 | width: 100%; 128 | } 129 | 130 | &__title { 131 | margin-top: $default-spacing; 132 | color: $foreground-lightest; 133 | } 134 | 135 | &__submit { 136 | width: 100%; 137 | height: 32px; 138 | text-align: center; 139 | border: none; 140 | outline: none; 141 | 142 | @include focus-states($accent-background-primary, $accent-foreground-primary); 143 | } 144 | } 145 | 146 | .file-template-item { 147 | padding: $default-spacing; 148 | @include focus-states($accent-background-primary, $accent-foreground-primary); 149 | 150 | &:hover, 151 | &:active { 152 | .file-template-item__title { 153 | color: $accent-foreground-primary; 154 | } 155 | 156 | .file-template-item__description { 157 | color: $accent-foreground-primary; 158 | } 159 | } 160 | 161 | &--selected { 162 | background-color: darken($accent-background-primary, 10%); 163 | 164 | .file-template-item__title { 165 | color: $accent-foreground-primary; 166 | } 167 | 168 | .file-template-item__description { 169 | color: $accent-foreground-primary; 170 | } 171 | } 172 | 173 | &__left { 174 | flex: 1 0 0px; 175 | } 176 | 177 | &__right { 178 | color: $foreground-white; 179 | } 180 | 181 | &__title { 182 | color: $foreground-lightest; 183 | margin-bottom: $default-spacing * 0.25; 184 | pointer-events: none; 185 | } 186 | 187 | &__description { 188 | color: $foreground-lighter; 189 | pointer-events: none; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/create/file-create.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { Observable, Subscription } from 'rxjs/Rx'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | import { Utilities } from '../../shared/helpers'; 5 | import { GithubService, WordService, NotificationService, ICommit } from '../../shared/services'; 6 | import { BaseComponent } from '../../shared/components'; 7 | import './file-create.component.scss'; 8 | declare var StringView: any; 9 | 10 | interface ITemplate { 11 | id: number, 12 | path: string, 13 | title: string, 14 | description: string 15 | } 16 | 17 | @Component({ 18 | selector: 'file-create', 19 | templateUrl: 'file-create.component.html' 20 | }) 21 | export class FileCreateComponent extends BaseComponent implements OnInit, OnDestroy { 22 | selectedOrg: string; 23 | selectedRepoName: string; 24 | selectedBranch: string; 25 | selectedPath: string; 26 | selectedFile: string; 27 | 28 | templates: ITemplate[]; 29 | selectedTemplate: ITemplate; 30 | 31 | newFolder: boolean; 32 | selectedFolder: string; 33 | 34 | constructor( 35 | private _router: Router, 36 | private _route: ActivatedRoute, 37 | private _githubService: GithubService, 38 | private _wordService: WordService, 39 | private _notificationService: NotificationService 40 | ) { 41 | super(); 42 | 43 | this.templates = [ 44 | { 45 | id: 0, 46 | path: null, 47 | title: 'Empty', 48 | description: 'Creates a new empty markdown file', 49 | }, 50 | { 51 | id: 2, 52 | path: 'readme', 53 | title: 'Readme', 54 | description: 'Creates a new readme markdown file with all the required sections', 55 | }, 56 | { 57 | id: 3, 58 | path: 'object-definition', 59 | title: 'API Documentation', 60 | description: 'Creates a new api documentation markdown file with all the required sections', 61 | }, 62 | { 63 | id: 4, 64 | path: 'mit-license', 65 | title: 'License', 66 | description: 'Creates a new MIT license markdown file', 67 | }, 68 | { 69 | id: 5, 70 | path: 'contributing', 71 | title: 'Contribution', 72 | description: 'Creates a new contribution markdown file', 73 | } 74 | ]; 75 | 76 | this.selectedTemplate = _.first(this.templates); 77 | } 78 | 79 | ngOnInit() { 80 | var subscription = this._route.params.subscribe(params => { 81 | this.selectedRepoName = params['repo']; 82 | this.selectedOrg = params['org']; 83 | this.selectedBranch = params['branch']; 84 | this.selectedPath = decodeURIComponent(params['path']); 85 | this.selectedPath = this.selectedPath === '#!/' ? null : this.selectedPath; 86 | }); 87 | 88 | this.markDispose(subscription); 89 | } 90 | 91 | back = () => Utilities.isNull(this.selectedPath) ? 92 | this._router.navigate([this.selectedOrg, this.selectedRepoName, this.selectedBranch]) : 93 | this._router.navigate([this.selectedOrg, this.selectedRepoName, this.selectedBranch, this.selectedPath]); 94 | 95 | create() { 96 | if (Utilities.isEmpty(this.selectedFile)) return null; 97 | 98 | var path = ''; 99 | if (!Utilities.isEmpty(this.selectedPath)) path += this.selectedPath + '/'; 100 | path += (this.newFolder && !Utilities.isEmpty(this.selectedFolder) ? this.selectedFolder.replace(/\s/g, '-') + '/' : '') + this.selectedFile.replace(/\s/g, '-') + '.md'; 101 | 102 | appInsights.trackEvent('Create new file', { "template": this.selectedTemplate.title }); 103 | 104 | this._githubService.getFileData(this.selectedTemplate.path).toPromise() 105 | .then(templateContent => { 106 | var base64Content = new StringView(templateContent, "UTF-8").toBase64().replace(/(?:\r\n|\r|\n)/g, ''); 107 | this._wordService.insertTemplate(templateContent, `https://raw.githubusercontent.com/${this.selectedOrg}/${this.selectedRepoName}/${this.selectedBranch}`); 108 | return { 109 | message: 'Creating ' + this.selectedFile + '.md', 110 | content: base64Content, 111 | branch: this.selectedBranch 112 | }; 113 | }) 114 | .then(body => this._githubService.createFile(this.selectedOrg, this.selectedRepoName, path, body).toPromise()) 115 | .then(response => this._router.navigate([this.selectedOrg, this.selectedRepoName, this.selectedBranch, encodeURIComponent(path), 'detail'])) 116 | .then(() => this._notificationService.toast(`${this.selectedFile} was created successfully`, 'File created')) 117 | .catch(exception => this._notificationService.error(exception)); 118 | } 119 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/detail/file-detail.component.html: -------------------------------------------------------------------------------- 1 | 
2 |

{{selectedFile}}

3 | 13 |
14 |
15 | 16 |
17 | 18 | 19 |
20 | View on GitHub 21 |
22 | 23 |
24 |
25 | 26 |
27 |
-------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/detail/file-detail.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixins.scss'; 2 | @import '../../../assets/styles/theme.scss'; 3 | 4 | .file-detail { 5 | &__content { 6 | padding: 0 $default-spacing; 7 | flex: 1 1 auto; 8 | } 9 | 10 | &__subtitle { 11 | margin-top: $default-spacing/2; 12 | margin-bottom: $default-spacing; 13 | } 14 | 15 | &__actions { 16 | @include flex(); 17 | padding: $default-spacing; 18 | 19 | &--row { 20 | @include inline-flex(); 21 | } 22 | } 23 | 24 | &__action { 25 | border: solid 1px $foreground-primary; 26 | outline: none; 27 | color: $foreground-primary; 28 | text-align: center; 29 | @include focus-states($foreground-primary, $background-light); 30 | margin-bottom: $default-spacing/2; 31 | flex: 1 0 auto; 32 | 33 | &--split { 34 | flex: 1 1 50%; 35 | @include ellipsis(); 36 | } 37 | 38 | &--link { 39 | margin: $default-spacing*2 0; 40 | border: none; 41 | outline: none; 42 | background: transparent; 43 | @include focus-states(transparent, $accent-background-primary); 44 | margin-bottom: $default-spacing; 45 | } 46 | 47 | &--primary { 48 | background-color: $accent-background-primary; 49 | color: $accent-foreground-primary; 50 | border-color: $accent-background-primary; 51 | width: 100%; 52 | } 53 | 54 | &--secondary { 55 | background-color: $foreground-white; 56 | color: $foreground-primary; 57 | width: 100%; 58 | 59 | &:last-child { 60 | margin-right: 0; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/detail/file-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { Observable } from 'rxjs/Rx'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | import { Utilities } from '../../shared/helpers'; 5 | import { GithubService, WordService, NotificationService, ICommit, IMessage, MessageAction, MessageType } from '../../shared/services'; 6 | import { BaseComponent } from '../../shared/components'; 7 | import './file-detail.component.scss'; 8 | 9 | declare var StringView: any; 10 | @Component({ 11 | selector: 'file-detail', 12 | templateUrl: 'file-detail.component.html' 13 | }) 14 | export class FileDetailComponent extends BaseComponent implements OnInit, OnDestroy { 15 | selectedOrg: string; 16 | selectedRepoName: string; 17 | selectedBranch: string; 18 | selectedPath: string; 19 | selectedFile: string; 20 | viewLink: string; 21 | 22 | private _file: any; 23 | private _sha: string; 24 | private _imageLink: string; 25 | commits: Observable 26 | 27 | constructor( 28 | private _router: Router, 29 | private _route: ActivatedRoute, 30 | private _githubService: GithubService, 31 | private _wordService: WordService, 32 | private _notificationService: NotificationService 33 | ) { 34 | super(); 35 | } 36 | 37 | ngOnInit() { 38 | var subscription1 = this._route.parent.params.subscribe(params => { 39 | this.selectedRepoName = params['repo']; 40 | this.selectedOrg = params['org']; 41 | }); 42 | 43 | var subscription2 = this._route.params.subscribe(params => { 44 | this.selectedBranch = params['branch']; 45 | this.selectedPath = Utilities.isEmpty(params['path']) ? '' : decodeURIComponent(params['path']); 46 | this.selectedFile = _.last(this.selectedPath.split('/')); 47 | this.viewLink = `https://github.com/${this.selectedOrg}/${this.selectedRepoName}/blob/${this.selectedBranch}/${this.selectedPath}`; 48 | this._imageLink = `https://raw.githubusercontent.com/${this.selectedOrg}/${this.selectedRepoName}/${this.selectedBranch}`; 49 | this._pull(); 50 | }); 51 | 52 | this.markDispose([subscription1, subscription2]); 53 | } 54 | 55 | push() { 56 | appInsights.trackEvent('push to github'); 57 | var start = performance.now(); 58 | this._wordService.getBase64EncodedStringsOfImages(this._imageLink) 59 | .then(images => { 60 | var hasInlinePictures = true; 61 | if (Utilities.isEmpty(images)) { return this._updateFile(false); } 62 | var promises = images.map(image => { 63 | hasInlinePictures = true; 64 | var body = { 65 | message: "Image Upload: " + new Date().toISOString() + " from Word to GitHub Add-in", 66 | content: image.base64ImageSrc.value, 67 | branch: this.selectedBranch 68 | }; 69 | 70 | return this._githubService.uploadImage(this.selectedOrg, this.selectedRepoName, image.hyperlink, body).toPromise(); 71 | }); 72 | 73 | promises.push(this._updateFile(hasInlinePictures) as any); 74 | return Promise.all(promises); 75 | }) 76 | .then(() => { 77 | this._notificationService.toast(`${this.selectedFile} was updated successfully`, 'File updated'); 78 | var end = performance.now(); 79 | appInsights.trackMetric('push duration', (end - start) / 1000); 80 | }) 81 | .catch(error => this._notificationService.error(error)); 82 | } 83 | 84 | pull() { 85 | appInsights.trackEvent('pull from github'); 86 | var actions = new MessageAction('Yes', 'No'); 87 | var subscription = actions.actionEvent.subscribe(result => { 88 | if (!result) return null; 89 | this._pull(); 90 | }); 91 | 92 | this._notificationService.message({ 93 | message: "Are you sure?", 94 | type: MessageType.Info, 95 | action: actions 96 | }); 97 | 98 | this.markDispose(subscription); 99 | } 100 | 101 | insertNumberedList() { 102 | appInsights.trackEvent('insert numbered list'); 103 | this._wordService.insertNumberedList(); 104 | } 105 | 106 | insertBulletedList() { 107 | appInsights.trackEvent('insert bulleted list'); 108 | this._wordService.insertBulletedList(); 109 | } 110 | 111 | private _pull() { 112 | var promises = [ 113 | this._githubService.getSha(this.selectedOrg, this.selectedRepoName, this.selectedBranch, this.selectedPath).toPromise(), 114 | this._githubService.file(this.selectedOrg, this.selectedRepoName, this.selectedBranch, this.selectedPath).toPromise() 115 | ] as any; 116 | 117 | return Promise.all(promises) 118 | .then(results => { 119 | var sha = results[0] && (results[0] as any).sha; 120 | this._file = results[1]; 121 | if (!this._sha) this._sha = sha; 122 | if (this._sha === sha) { 123 | this._wordService.insertHtml(this._file, this._imageLink); 124 | return this._file; 125 | } 126 | else { 127 | throw 'Merge conflit!'; 128 | } 129 | }) 130 | .catch(error => this._notificationService.error(error)); 131 | } 132 | 133 | private _updateFile(hasInlinePictures: boolean) { 134 | var promises = [ 135 | this._githubService.getSha(this.selectedOrg, this.selectedRepoName, this.selectedBranch, this.selectedPath).toPromise(), 136 | this._wordService.getMarkdown(this._imageLink, hasInlinePictures) 137 | ]; 138 | 139 | return Promise.all(promises) 140 | .then(results => { 141 | var file = results[0] as any; 142 | var md = results[1]; 143 | 144 | if (Utilities.isNull(md) || Utilities.isNull(file)) throw 'Couldn\'t get the markdown of current document'; 145 | if (this._sha !== file.sha) throw 'We noticed a merge conflict!'; 146 | 147 | var base64Data = new StringView(md, "UTF-8").toBase64().replace(/(?:\r\n|\r|\n)/g, ''); 148 | return { 149 | message: "Update: " + new Date().toISOString() + " from Word to GitHub Add-in", 150 | content: base64Data, 151 | branch: this.selectedBranch, 152 | sha: file.sha 153 | }; 154 | }) 155 | .then(body => this._githubService.updateFile(this.selectedOrg, this.selectedRepoName, this.selectedPath, body).toPromise()) 156 | .then(response => this._sha = response.content.sha) 157 | .catch(error => this._notificationService.error(error)); 158 | } 159 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/file.routes.ts: -------------------------------------------------------------------------------- 1 | import { provideRoutes, RouterModule, Routes } from '@angular/router'; 2 | import { FileCreateComponent } from './create/file-create.component'; 3 | import { FileDetailComponent } from './detail/file-detail.component'; 4 | import { FileListComponent } from './list/file-list.component'; 5 | import { FileTreeComponent } from './tree/file-tree.component'; 6 | import { AuthGuard } from '../shared/services'; 7 | 8 | const FileRoutes: Routes = [ 9 | { 10 | path: ':org/:repo', 11 | component: FileListComponent, 12 | canActivate: [AuthGuard], 13 | children: [ 14 | { 15 | path: ':branch', 16 | component: FileTreeComponent 17 | }, 18 | { 19 | path: ':branch/:path', 20 | component: FileTreeComponent 21 | }, 22 | { 23 | path: ':branch/:path/detail', 24 | component: FileDetailComponent 25 | }, 26 | { 27 | path: ':branch/:path/detail', 28 | component: FileDetailComponent 29 | } 30 | ] 31 | }, 32 | { 33 | path: ':org/:repo/:branch/create', 34 | component: FileCreateComponent 35 | }, 36 | { 37 | path: ':org/:repo/:branch/:path/create', 38 | component: FileCreateComponent 39 | } 40 | ]; 41 | 42 | export const FILE_ROUTES = RouterModule.forChild(FileRoutes); 43 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create/file-create.component'; 2 | export * from './detail/file-detail.component'; 3 | export * from './list/file-list.component'; 4 | export * from './tree/file-tree.component'; 5 | export * from './file.routes'; -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/list/file-list.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
    3 |
  • 4 |
5 |
6 | 7 |
8 |

{{selectedRepoName | safenames}}

9 | 14 |
15 | 16 |
17 | 18 | 19 |
-------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/list/file-list.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixins.scss'; 2 | @import '../../../assets/styles/theme.scss'; 3 | 4 | .file-list-component { 5 | &__separator { 6 | margin: 0 auto; 7 | width: calc(100% - 30px); 8 | border: none; 9 | border-top: 1px solid $background-light; 10 | } 11 | 12 | &__branches { 13 | padding: $default-spacing; 14 | 15 | h2 { 16 | padding-bottom: $default-spacing; 17 | } 18 | 19 | select { 20 | border: 1px solid $background-light; 21 | padding: $default-spacing/2; 22 | width: 100%; 23 | } 24 | } 25 | 26 | &__files { 27 | flex: 1 1 auto; 28 | @include flex(); 29 | } 30 | } 31 | 32 | .file-list-command-bar { 33 | @include inline-flex(); 34 | list-style-type: none; 35 | width: 100%; 36 | border-bottom: 1px solid $background-light; 37 | 38 | &__command { 39 | padding: (40px - $default-spacing)/2 $default-spacing; 40 | @include focus-states($accent-background-primary, $accent-foreground-primary); 41 | 42 | &:hover i { 43 | color: $accent-foreground-primary; 44 | } 45 | 46 | &--text { 47 | flex: 1 1 0px; 48 | @include ellipsis(); 49 | 50 | i { 51 | padding-right: $default-spacing/2; 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/list/file-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { Observable } from 'rxjs/Rx'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | import { GithubService, MediatorService, IBranch, IEventChannel } from '../../shared/services'; 5 | import { BaseComponent } from '../../shared/components'; 6 | import './file-list.component.scss'; 7 | 8 | @Component({ 9 | selector: 'file-list', 10 | templateUrl: 'file-list.component.html' 11 | }) 12 | export class FileListComponent extends BaseComponent implements OnInit, OnDestroy { 13 | selectedOrg: string; 14 | selectedRepoName: string; 15 | selectedBranch: IBranch; 16 | branches: Observable; 17 | channel: IEventChannel; 18 | 19 | constructor( 20 | private _githubService: GithubService, 21 | private _mediatorService: MediatorService, 22 | private _router: Router, 23 | private _route: ActivatedRoute 24 | ) { 25 | super(); 26 | this.channel = this._mediatorService.createEventChannel('hamburger'); 27 | } 28 | 29 | ngOnInit() { 30 | var subscription = this._route.params.subscribe(params => { 31 | this.selectedRepoName = params['repo']; 32 | this.selectedOrg = params['org']; 33 | this.selectedBranch = { name: 'master' }; 34 | this.branches = this._githubService.branches(this.selectedOrg, this.selectedRepoName); 35 | }); 36 | 37 | this.markDispose(subscription); 38 | } 39 | 40 | selectBranch(branchName: string) { 41 | appInsights.trackEvent('switch branch'); 42 | this._router.navigate([this.selectedOrg, this.selectedRepoName, branchName]); 43 | this.selectedBranch.name = branchName; 44 | } 45 | 46 | showMenu() { 47 | this.channel.source.next(true); 48 | } 49 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/tree/file-tree.component.html: -------------------------------------------------------------------------------- 1 | 
2 | 6 |
    7 |
  • Seems like there are no 'Markdown' files in this folder. You could either create one or choose a different folder.
  • 8 |
  • 9 |
    10 |
    11 |

    {{file.name}}

    12 |
    13 |
  • 14 |
  • 15 |
  • 16 |
    17 |
    18 |

    Add a new file

    19 |
    20 |
  • 21 |
22 |
23 |
-------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/tree/file-tree.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixins.scss'; 2 | @import '../../../assets/styles/theme.scss'; 3 | 4 | .file-tree-component { 5 | @include flex(); 6 | @include vScroll(); 7 | height: 100vh; 8 | 9 | &__title { 10 | padding: $default-spacing; 11 | } 12 | 13 | &__message { 14 | padding: $default-spacing; 15 | } 16 | 17 | &__list { 18 | height: 100%; 19 | } 20 | } 21 | 22 | .file-tree-item { 23 | @include inline-flex(); 24 | padding: $default-spacing; 25 | width: 100%; 26 | cursor: pointer; 27 | box-shadow: none; 28 | 29 | @include focus-states($background-lightest); 30 | 31 | &__primary-text { 32 | @include ellipsis(); 33 | position: static; 34 | padding: 0; 35 | } 36 | 37 | &__content { 38 | flex: 1 0 0px; 39 | padding-right: $default-spacing; 40 | } 41 | 42 | &__icon { 43 | position: static; 44 | padding-right: $default-spacing; 45 | color: rgba($foreground-primary, 0.8); 46 | 47 | i { 48 | top: 2px; 49 | } 50 | } 51 | 52 | &--separator { 53 | padding: 0; 54 | border-bottom: 1px solid $background-light; 55 | } 56 | 57 | &--alternate { 58 | @include focus-states($accent-background-primary, $accent-foreground-primary); 59 | 60 | &:hover p, 61 | &:hover i { 62 | color: $accent-foreground-primary; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/file/tree/file-tree.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { Observable } from 'rxjs/Rx'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | import { GithubService, IContents, WordService } from '../../shared/services'; 5 | import { Utilities } from '../../shared/helpers'; 6 | import { BaseComponent } from '../../shared/components'; 7 | import './file-tree.component.scss'; 8 | 9 | @Component({ 10 | selector: 'file-tree', 11 | templateUrl: 'file-tree.component.html' 12 | }) 13 | export class FileTreeComponent extends BaseComponent implements OnInit, OnDestroy { 14 | selectedOrg: string; 15 | selectedRepoName: string; 16 | selectedBranch: string; 17 | selectedPath: string; 18 | files: Observable; 19 | 20 | constructor( 21 | private _githubService: GithubService, 22 | private _wordService: WordService, 23 | private _router: Router, 24 | private _route: ActivatedRoute 25 | ) { 26 | super(); 27 | } 28 | 29 | select(item: IContents) { 30 | if (item.type === 'dir') { 31 | this._router.navigate([this.selectedOrg, this.selectedRepoName, this.selectedBranch, encodeURIComponent(item.path)]); 32 | } 33 | else { 34 | appInsights.trackEvent('select file to display'); 35 | this._router.navigate([this.selectedOrg, this.selectedRepoName, this.selectedBranch, encodeURIComponent(item.path), 'detail']); 36 | } 37 | } 38 | 39 | ngOnInit() { 40 | var subscription1 = this._route.parent.params.subscribe(params => { 41 | this.selectedRepoName = params['repo']; 42 | this.selectedOrg = params['org']; 43 | }); 44 | 45 | var subscription2 = this._route.params.subscribe(params => { 46 | this.selectedBranch = params['branch']; 47 | this.selectedPath = Utilities.isEmpty(params['path']) ? '' : decodeURIComponent(params['path']); 48 | this.files = this._githubService.files(this.selectedOrg, this.selectedRepoName, this.selectedBranch, this.selectedPath); 49 | }); 50 | 51 | this.markDispose([subscription1, subscription2]); 52 | } 53 | 54 | createFile() { 55 | var path = Utilities.isEmpty(this.selectedPath) ? '#!/' : this.selectedPath; 56 | this._router.navigate([this.selectedOrg, this.selectedRepoName, this.selectedBranch, encodeURIComponent(path), 'create']); 57 | } 58 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/login/index.ts: -------------------------------------------------------------------------------- 1 | export * from './login.component'; -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 | 
2 | 31 |
-------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/login/login.component.scss: -------------------------------------------------------------------------------- 1 | .ms-firstrun-login { 2 | position: absolute; 3 | z-index: 999; 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | height: 100%; 8 | width: 100%; 9 | text-align: center; 10 | align-items: center; 11 | background-color: #212121; 12 | } 13 | 14 | .ms-firstrun-login__brand { 15 | display: flex; 16 | flex-direction: column; 17 | flex-wrap: nowrap; 18 | justify-content: flex-end; 19 | align-items: center; 20 | flex: 1 1 auto; 21 | } 22 | 23 | .ms-firstrun-login__brand h1 { 24 | letter-spacing: 1px; 25 | } 26 | 27 | .ms-firstrun-login__image { 28 | display: flex; 29 | flex-direction: column; 30 | flex-wrap: nowrap; 31 | align-items: center; 32 | flex: 0 1 0; 33 | } 34 | 35 | #word-github-logo { 36 | background: transparent no-repeat center center; 37 | background-image: url("../../assets/images/logo.png"); 38 | background-size: cover; 39 | width: 90px; 40 | height: 90px; 41 | margin-bottom: 20px; 42 | } 43 | 44 | .ms-firstrun-login__title { 45 | flex: 1 1 0; 46 | display: flex; 47 | flex-direction: column; 48 | flex-wrap: nowrap; 49 | justify-content: center; 50 | align-items: center; 51 | padding: 0px; 52 | } 53 | 54 | .ms-firstrun-login__centering-container { 55 | display: flex; 56 | flex-direction: column; 57 | flex-wrap: nowrap; 58 | justify-content: center; 59 | align-items: center; 60 | flex: 1 1 auto; 61 | } 62 | 63 | .ms-firstrun-login__action { 64 | display: flex; 65 | flex-direction: column; 66 | flex-wrap: nowrap; 67 | padding-left: 20px; 68 | padding-right: 20px; 69 | flex: 1 1 auto; 70 | } 71 | 72 | .ms-firstrun-login__content--centered { 73 | display: flex; 74 | flex-direction: column; 75 | flex-wrap: nowrap; 76 | margin-top: 5px; 77 | } 78 | 79 | .ms-firstrun-login__button { 80 | padding-top: 20px; 81 | display: flex; 82 | flex-direction: column; 83 | flex-wrap: nowrap; 84 | justify-content: center; 85 | align-items: center; 86 | -moz-transition: background ease 0.1s, color ease 0.1s; 87 | -o-transition: background ease 0.1s, color ease 0.1s; 88 | -webkit-transition: background ease 0.1s, color ease 0.1s; 89 | transition: background ease 0.1s, color ease 0.1s; 90 | } 91 | 92 | .ms-firstrun-login__centering-container { 93 | display: flex; 94 | flex-direction: column; 95 | flex-wrap: nowrap; 96 | justify-content: center; 97 | align-items: center; 98 | 99 | .ms-progress-component__title { 100 | margin-bottom: 35px; 101 | } 102 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { GithubService } from '../shared/services'; 4 | import { Utilities } from '../shared/helpers'; 5 | import './login.component.scss'; 6 | 7 | @Component({ 8 | selector: 'login', 9 | templateUrl: 'login.component.html' 10 | }) 11 | export class LoginComponent { 12 | loading: string; 13 | progress: boolean; 14 | 15 | constructor( 16 | private _githubService: GithubService, 17 | private _router: Router 18 | ) { 19 | } 20 | 21 | login() { 22 | appInsights.trackEvent('login to github'); 23 | appInsights.clearAuthenticatedUserContext(); 24 | this.loading = "Loading your GitHub profile"; 25 | this.progress = true; 26 | var start = performance.now(); 27 | 28 | this._githubService.login() 29 | .then(profile => { 30 | this.loading = null; 31 | this.progress = false; 32 | var end = performance.now(); 33 | appInsights.setAuthenticatedUserContext(profile.user.login); 34 | appInsights.trackMetric('login duration', (end - start) / 1000); 35 | this._router.navigate(['']); 36 | }) 37 | .catch(error => { 38 | debugger; 39 | this.loading = "Login failed. Please try again later." 40 | this.progress = false; 41 | Utilities.error(error); 42 | }); 43 | } 44 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/repo/index.ts: -------------------------------------------------------------------------------- 1 | export * from './repo.component'; -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/repo/repo.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
    3 |
  • 4 |
5 |
6 | 7 |
8 |

Repositories

9 | 10 | 14 | 15 |
    16 |
  • 17 |
    18 |

    {{repository.name | safenames}}

    19 |

    @{{repository.owner.login}}

    20 |

    {{repository.description}}

    21 |
    22 |
    23 | 24 |
    25 |
  • 26 |
27 | 28 |
29 |
-------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/repo/repo.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../assets/styles/mixins.scss'; 2 | @import '../../assets/styles/theme.scss'; 3 | 4 | .repo-list-component { 5 | &__search { 6 | width: 100%; 7 | margin: 0; 8 | } 9 | 10 | &__separator { 11 | margin: 0 auto; 12 | width: calc(100% - 30px); 13 | border: none; 14 | border-top: 1px solid $background-light; 15 | } 16 | 17 | &__repos { 18 | @include vScroll(); 19 | @include flex(); 20 | 21 | h2 { 22 | padding: $default-spacing $default-spacing; 23 | } 24 | } 25 | 26 | &__load { 27 | width: 100%; 28 | height: 32px; 29 | text-align: center; 30 | border: none; 31 | outline: none; 32 | 33 | background-color: $accent-background-primary; 34 | color: $accent-foreground-primary; 35 | 36 | @include focus-states($background-primary, $foreground-lighter); 37 | } 38 | } 39 | 40 | .repo-list-command-bar { 41 | @include inline-flex(); 42 | list-style-type: none; 43 | width: 100%; 44 | border-bottom: 1px solid $background-light; 45 | 46 | &__command { 47 | padding: (40px - $default-spacing)/2 $default-spacing; 48 | @include focus-states($accent-background-primary, $accent-foreground-primary); 49 | 50 | &:hover i { 51 | color: $accent-foreground-primary; 52 | } 53 | 54 | &--text { 55 | flex: 1 1 0px; 56 | @include ellipsis(); 57 | 58 | i { 59 | padding-right: $default-spacing/2; 60 | } 61 | } 62 | } 63 | } 64 | 65 | .repo-list-search { 66 | &__input { 67 | width: 100%; 68 | color: $foreground-primary; 69 | border: none; 70 | border-bottom: solid 1px $background-light; 71 | 72 | &:focus { 73 | background-color: $background-lightest; 74 | } 75 | } 76 | 77 | &__label { 78 | @include ellipsis(); 79 | width: 100%; 80 | line-height: 32px; /* height of search box */ 81 | } 82 | } 83 | 84 | .repo-list-item { 85 | @include inline-flex(); 86 | padding: $default-spacing/2 $default-spacing; 87 | width: 100%; 88 | cursor: pointer; 89 | 90 | @include focus-states($background-lightest); 91 | 92 | &__primary-text { 93 | @include ellipsis(); 94 | padding-right: 0; 95 | } 96 | 97 | &__secondary-text { 98 | @include ellipsis(); 99 | padding-right: 0; 100 | } 101 | 102 | &__tertiary-text { 103 | padding-right: 0; 104 | word-wrap: break-word; 105 | white-space: pre-wrap; 106 | } 107 | 108 | &__content { 109 | flex: 1 1 0px; 110 | } 111 | 112 | &__action { 113 | width: auto; 114 | height: auto; 115 | position: static; 116 | 117 | i { 118 | position: static; 119 | } 120 | } 121 | } 122 | 123 | .page-nav { 124 | cursor:pointer; 125 | padding: 20px; 126 | } 127 | 128 | #next { 129 | padding-left: 209px; 130 | } 131 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/repo/repo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable, Subject } from 'rxjs/Rx'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | import { GithubService, MediatorService, FavoritesService, IRepository, IRepositoryCollection, IEventChannel } from '../shared/services'; 5 | import { Utilities } from '../shared/helpers'; 6 | import { SafeNamesPipe } from '../shared/pipes'; 7 | import { BaseComponent } from '../shared/components'; 8 | import './repo.component.scss'; 9 | 10 | @Component({ 11 | selector: 'repo', 12 | templateUrl: 'repo.component.html' 13 | }) 14 | export class RepoComponent extends BaseComponent implements OnInit { 15 | private _page = 0; 16 | 17 | repositories: IRepository[] = []; 18 | selectedOrg: string; 19 | channel: IEventChannel; 20 | loadComplete: boolean; 21 | 22 | constructor( 23 | private _githubService: GithubService, 24 | private _mediatorService: MediatorService, 25 | private _router: Router, 26 | private _route: ActivatedRoute, 27 | private _favoritesService: FavoritesService 28 | ) { 29 | super(); 30 | this.channel = this._mediatorService.createEventChannel('hamburger'); 31 | } 32 | 33 | ngOnInit() { 34 | var sub = this._route.params.subscribe(params => { 35 | this.selectedOrg = params['org'] || this._githubService.profile.user.login; 36 | this.load(true); 37 | }); 38 | 39 | this.markDispose(sub); 40 | } 41 | 42 | selectRepo(repository: IRepository) { 43 | appInsights.trackEvent('select repository from list'); 44 | this._router.navigate([repository.owner.login, repository.name, 'master']); 45 | } 46 | 47 | pin(item: IRepository) { 48 | appInsights.trackEvent('pin repository from list'); 49 | item.isPinned = true; 50 | this._favoritesService.pin(item); 51 | } 52 | 53 | load(clear: boolean = false) { 54 | appInsights.trackEvent('load repositories', null, { "maxPage": this._page }); 55 | if (clear) { 56 | this.repositories = []; 57 | this.loadComplete = false; 58 | } 59 | var personal = this.selectedOrg === this._githubService.profile.user.login; 60 | var sub = this._githubService 61 | .repos(this._page++, this.selectedOrg, personal) 62 | .subscribe(data => { 63 | if (Utilities.isEmpty(data)) { 64 | this.loadComplete = true; 65 | return; 66 | } 67 | this.repositories = this.repositories.concat(data); 68 | }); 69 | 70 | this.markDispose(sub); 71 | } 72 | 73 | showMenu() { 74 | this.channel.source.next(true); 75 | } 76 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/base.component.ts: -------------------------------------------------------------------------------- 1 | import { Subscription } from 'rxjs/Rx'; 2 | import { Utilities } from '../helpers'; 3 | import { IEventChannel, ISubjectChannel } from '../services'; 4 | 5 | export class BaseComponent { 6 | private _subscriptions: Subscription[] = []; 7 | 8 | protected markDispose(subscription: Subscription[]) 9 | protected markDispose(subscription: Subscription) 10 | protected markDispose(subscription: any) { 11 | if (Array.isArray(subscription)) { 12 | this._subscriptions = this._subscriptions.concat(subscription); 13 | } 14 | else if (subscription instanceof Subscription) { 15 | this._subscriptions.push(subscription); 16 | } 17 | } 18 | 19 | ngOnDestroy() { 20 | _.each(this._subscriptions, subscription => { 21 | subscription.unsubscribe(); 22 | }); 23 | 24 | this._subscriptions = []; 25 | } 26 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/breadcrumb/breadcrumb.component.html: -------------------------------------------------------------------------------- 1 |  12 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/breadcrumb/breadcrumb.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixins.scss'; 2 | @import '../../../../assets/styles/theme.scss'; 3 | 4 | .breadcrumb-component { 5 | @include inline-flex(null, center); 6 | position: relative; 7 | padding: $default-spacing $default-spacing/2; 8 | 9 | &__ellipsis { 10 | position: relative; 11 | top: 4px; 12 | } 13 | 14 | &__list { 15 | list-style-type: none; 16 | @include inline-flex(); 17 | 18 | &-item { 19 | padding: $default-spacing/2; 20 | @include focus-states(transparent, $accent-background-primary); 21 | @include ellipsis(); 22 | 23 | &--ellipsis-container:active > .breadcrumb-component__list--overflow, 24 | &--ellipsis-container:focus > .breadcrumb-component__list--overflow { 25 | display: block; 26 | } 27 | 28 | &:hover::before { 29 | color: $foreground-primary; 30 | } 31 | 32 | &::before { 33 | content: "\e08a"; 34 | font-family: "Office365Icons"; 35 | line-height: 1; 36 | speak: none; 37 | position: relative; 38 | padding-right: $default-spacing * 0.75; 39 | } 40 | 41 | &:first-child::before { 42 | display: none; 43 | } 44 | } 45 | 46 | &--overflow { 47 | @include flex(); 48 | display: none; 49 | background-color: $background-white; 50 | position: absolute; 51 | top: 2.5 * $default-spacing; 52 | left: 0; 53 | box-shadow: 0 0 10px 0 rgba(#000, 0.4); 54 | z-index: 998; 55 | 56 | &:hover, &:active, &:focus { 57 | display: block; 58 | } 59 | 60 | .breadcrumb-component__list-item { 61 | @include focus-states($background-lightest, $accent-background-primary); 62 | padding: $default-spacing * 0.75 $default-spacing; 63 | overflow: auto; 64 | max-width: none; 65 | 66 | &::before { 67 | display: none; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/breadcrumb/breadcrumb.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core'; 2 | import { Router, NavigationEnd } from '@angular/router'; 3 | import { Observable, Subscription } from 'rxjs/Rx'; 4 | import { IBreadcrumb } from '../../services'; 5 | import { Utilities } from '../../helpers'; 6 | import { BaseComponent } from '../base.component'; 7 | import './breadcrumb.component.scss'; 8 | 9 | @Component({ 10 | selector: 'breadcrumb', 11 | templateUrl: 'breadcrumb.component.html' 12 | }) 13 | export class BreadcrumbComponent extends BaseComponent implements OnDestroy { 14 | private _breadcrumbs: IBreadcrumb[] = []; 15 | private _max: number; 16 | 17 | isOverflown: boolean; 18 | breadcrumbs: IBreadcrumb[]; 19 | overflownBreadcrumbs: IBreadcrumb[]; 20 | 21 | constructor(private _router: Router) { 22 | super(); 23 | } 24 | 25 | @Input() 26 | set max(value: number) { 27 | this._max = value <= 0 ? 4 : value; 28 | }; 29 | 30 | get max(): number { 31 | return this._max; 32 | } 33 | 34 | click(breadcrumb: IBreadcrumb) { 35 | if (Utilities.isNull(breadcrumb.href)) return; 36 | appInsights.trackEvent('Breadcrumb clicked', { "name": breadcrumb.text }); 37 | this._router.navigate(breadcrumb.href as string[]); 38 | } 39 | 40 | ngOnInit() { 41 | this._generateFromUrl(this._router.url); 42 | this._recompute(); 43 | 44 | var subscription = this._router.events.subscribe(event => { 45 | if (event instanceof NavigationEnd) { 46 | this._generateFromUrl(this._router.url); 47 | this._recompute(); 48 | } 49 | }); 50 | 51 | this.markDispose(subscription); 52 | } 53 | 54 | private _generateFromUrl(url: string) { 55 | this._breadcrumbs = []; 56 | var routerLinkArray = decodeURIComponent(url).split('/'); 57 | var path = _.last(routerLinkArray); 58 | var detail = path === 'detail'; 59 | path = detail ? routerLinkArray[4] : path; 60 | 61 | var href = _.rest(_.first(routerLinkArray, 4)) as string[]; 62 | var branch = _.last(href); 63 | 64 | if (!Utilities.isNull(branch)) { 65 | this._breadcrumbs.push({ 66 | key: 0, 67 | text: branch, 68 | href: href 69 | }); 70 | } 71 | 72 | if (!Utilities.isNull(path) && path !== branch) { 73 | var decodedSegments = decodeURIComponent(path as string).split('/'); 74 | _.each(decodedSegments, (segment, i) => { 75 | var pathToSegment = encodeURIComponent(_.first(decodedSegments, i + 1).join('/')); 76 | this._breadcrumbs.push({ 77 | key: i + 1, 78 | text: segment, 79 | href: [...href, pathToSegment] 80 | }); 81 | }); 82 | } 83 | 84 | if (detail) { 85 | var file = _.last(this._breadcrumbs); 86 | file.href = null; 87 | } 88 | } 89 | 90 | private _recompute() { 91 | if (Utilities.isEmpty(this._breadcrumbs)) { 92 | this.breadcrumbs = null; 93 | this.overflownBreadcrumbs = null; 94 | return; 95 | }; 96 | 97 | if (this._breadcrumbs.length > this.max) { 98 | this.overflownBreadcrumbs = _.first(this._breadcrumbs, this._breadcrumbs.length - this.max); 99 | this.breadcrumbs = _.last(this._breadcrumbs, this.max); 100 | } 101 | else { 102 | this.breadcrumbs = this._breadcrumbs; 103 | this.overflownBreadcrumbs = null; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/hamburger/hamburger.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 |
4 |
5 |

{{profile?.user?.name}}

6 |

@{{profile?.user?.login}}

7 |

{{profile?.token?.access_token}}

8 |
9 |
10 | 23 | 34 |
35 |
36 | 39 |
40 |
-------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/hamburger/hamburger.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixins.scss'; 2 | @import '../../../../assets/styles/theme.scss'; 3 | 4 | .hamburger-menu { 5 | position: fixed; 6 | z-index: 999; 7 | height: 100%; 8 | width: 100%; 9 | max-width: 350px; 10 | top: 0; 11 | left: 0; 12 | background-color: $background-primary; 13 | @include flex(); 14 | transform: translate3d(-100%, 0, 0); 15 | transition: transform 0.4s cubic-bezier(0,1.13,.4,1); 16 | 17 | &__close { 18 | width: 20px; 19 | padding: $default-spacing; 20 | color: $foreground-light; 21 | @include focus-states($foreground-primary, $accent-background-primary); 22 | 23 | i { 24 | top: 0; 25 | } 26 | } 27 | 28 | &__profile { 29 | @include center-flex(); 30 | padding: $default-spacing; 31 | } 32 | 33 | &__container { 34 | flex: 1 1 0px; 35 | height: 100%; 36 | overflow: auto; 37 | overflow-x: hidden; 38 | } 39 | 40 | &--shown { 41 | transform: translate3d(0, 0, 0); 42 | } 43 | } 44 | 45 | .profile { 46 | &__image { 47 | background: transparent no-repeat center center; 48 | background-size: contain; 49 | width: 120px; 50 | height: 120px; 51 | border-radius: 100%; 52 | } 53 | 54 | &__name { 55 | color: $background-lightest; 56 | } 57 | 58 | &__handle { 59 | color: rgba($background-lightest, 0.6); 60 | margin-bottom: $default-spacing; 61 | } 62 | } 63 | 64 | .menu-list { 65 | list-style-type: none; 66 | margin-bottom: 2 * $default-spacing; 67 | 68 | &__title { 69 | color: rgba($foreground-lighter, 0.8); 70 | padding: 0 $default-spacing; 71 | margin-bottom: $default-spacing/2; 72 | 73 | i { 74 | margin-right: $default-spacing * 0.75; 75 | top: 2px; 76 | } 77 | } 78 | 79 | &__message { 80 | color: $foreground-light; 81 | padding: 0 $default-spacing; 82 | } 83 | 84 | &__item { 85 | padding: $default-spacing $default-spacing; 86 | color: $foreground-lightest; 87 | position: relative; 88 | @include focus-states($foreground-primary, $accent-background-primary); 89 | 90 | p { 91 | &:first-child { 92 | margin-bottom: 4px; 93 | } 94 | 95 | padding-right: 30px; 96 | color: $background-lighter; 97 | } 98 | 99 | &--subtle { 100 | color: rgba($foreground-lighter, 0.8); 101 | 102 | i { 103 | top: 2px; 104 | padding-right: $default-spacing * 0.75; 105 | } 106 | } 107 | 108 | &--inline-image { 109 | width: 100%; 110 | @include inline-flex(null, center); 111 | 112 | p { 113 | color: $background-lighter; 114 | } 115 | } 116 | 117 | &--action { 118 | display: block; 119 | position: absolute; 120 | top: 17px; 121 | right: 20px; 122 | 123 | i { 124 | top: 0; 125 | } 126 | } 127 | } 128 | 129 | &__thumb { 130 | background: transparent no-repeat center center; 131 | background-size: contain; 132 | width: 24px; 133 | height: 24px; 134 | margin-right: $default-spacing; 135 | } 136 | 137 | &__overflow { 138 | padding: 0 $default-spacing; 139 | margin-top: $default-spacing / 2; 140 | color: $foreground-light; 141 | 142 | @include focus-states(transparent, $accent-background-primary); 143 | } 144 | 145 | &--footer { 146 | margin-bottom: 0; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/hamburger/hamburger.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Observable } from 'rxjs/Rx'; 4 | import { Utilities } from '../../helpers'; 5 | import { GithubService, MediatorService, FavoritesService, IUserProfile, IProfile, IRepository, IEventChannel } from '../../services'; 6 | import { BaseComponent } from '../base.component'; 7 | import './hamburger.component.scss'; 8 | 9 | @Component({ 10 | selector: 'hamburger', 11 | templateUrl: 'hamburger.component.html' 12 | }) 13 | export class HamburgerComponent extends BaseComponent implements OnInit, OnDestroy { 14 | channel: IEventChannel; 15 | isShown: Observable; 16 | favoriteRepositories: IRepository[]; 17 | 18 | constructor( 19 | private _githubService: GithubService, 20 | private _mediatorService: MediatorService, 21 | private _router: Router, 22 | private _favoritesService: FavoritesService 23 | ) { 24 | super(); 25 | this.channel = this._mediatorService.createEventChannel('hamburger'); 26 | } 27 | 28 | ngOnInit() { 29 | this.isShown = this.channel.source$; 30 | this.favoriteRepositories = this._favoritesService.values(); 31 | this._favoritesService.pushDataEvent.source$.subscribe(next => { 32 | this.favoriteRepositories = this._favoritesService.values(); 33 | }); 34 | } 35 | 36 | get profile(): IUserProfile { 37 | return this._githubService.profile; 38 | } 39 | 40 | closeMenu() { 41 | this.channel.source.next(false); 42 | } 43 | 44 | selectRepository(repository: IRepository) { 45 | appInsights.trackEvent('Navigate to repository from hamburger'); 46 | this._router.navigate([repository.owner.login, repository.name, 'master']); 47 | this.closeMenu(); 48 | } 49 | 50 | selectOrg(org: IProfile) { 51 | if (Utilities.isNull(org)) { 52 | appInsights.trackEvent('Navigate to org from hamburger'); 53 | this._router.navigate(['']); 54 | } 55 | else { 56 | this._router.navigate([org.login]); 57 | } 58 | this.closeMenu(); 59 | } 60 | 61 | unpin(repository: IRepository) { 62 | appInsights.trackEvent('Unpin repository from hamburger'); 63 | this._favoritesService.unpin(repository); 64 | repository.isPinned = false; 65 | } 66 | 67 | signOut() { 68 | appInsights.trackEvent('Logout from hamburger'); 69 | appInsights.clearAuthenticatedUserContext(); 70 | this._githubService.logout(); 71 | this._router.navigate(['/login']); 72 | this.closeMenu(); 73 | } 74 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/index.ts: -------------------------------------------------------------------------------- 1 | import { HamburgerComponent } from './hamburger/hamburger.component'; 2 | import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component'; 3 | import { MessageBarComponent } from './notification/message-bar.component'; 4 | import { ToastComponent } from './notification/toast.component'; 5 | import { AsyncViewComponent } from './notification/async-view.component'; 6 | import { BaseComponent } from './base.component'; 7 | 8 | const COMPONENTS = [ 9 | HamburgerComponent, 10 | BreadcrumbComponent, 11 | MessageBarComponent, 12 | ToastComponent, 13 | AsyncViewComponent 14 | ]; 15 | 16 | export { COMPONENTS, HamburgerComponent, BreadcrumbComponent, MessageBarComponent, ToastComponent, AsyncViewComponent, BaseComponent }; -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/notification/async-view.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{loading}}

4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |

{{error}}

13 |
14 |
15 |
16 |

{{placeholder}}

17 |
18 | 19 |
20 |
-------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/notification/async-view.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixins.scss'; 2 | @import '../../../../assets/styles/theme.scss'; 3 | 4 | async-view { 5 | height: 100%; 6 | } 7 | 8 | .async-view { 9 | background-color: $foreground-white !important; 10 | flex: 1 1 100%; 11 | 12 | &__content { 13 | height: 100%; 14 | } 15 | 16 | &__error { 17 | margin-bottom: $default-spacing * 4; 18 | align-self: flex-start; 19 | 20 | &::after { 21 | content: ':('; 22 | margin-left: 10px; 23 | font-size: 10em; 24 | } 25 | } 26 | 27 | .ms-progress { 28 | margin-top: $default-spacing * 4; 29 | } 30 | 31 | .ms-progress .ms-Spinner { 32 | font-size: 7px; 33 | width: 1em; 34 | height: 1em; 35 | border-radius: 100%; 36 | @include animation(spinner-blue 0.7s infinite ease-in-out); 37 | transform: translateZ(0); 38 | margin-bottom: $default-spacing * 2; 39 | } 40 | } 41 | 42 | @include keyframes(spinner-blue) { 43 | 0%, 100% { 44 | box-shadow: 0em -2.6em 0em 0em rgba($accent-background-primary, 1), 1.8em -1.8em 0 0em rgba($accent-background-primary, 0.2), 2.5em 0em 0 0em rgba($accent-background-primary, 0.2), 1.75em 1.75em 0 0em rgba($accent-background-primary, 0.2), 0em 2.5em 0 0em rgba($accent-background-primary, 0.2), -1.8em 1.8em 0 0em rgba($accent-background-primary, 0.2), -2.6em 0em 0 0em rgba($accent-background-primary, 0.5), -1.8em -1.8em 0 0em rgba($accent-background-primary, 0.7); 45 | } 46 | 47 | 12.5% { 48 | box-shadow: 0em -2.6em 0em 0em rgba($accent-background-primary, 0.7), 1.8em -1.8em 0 0em rgba($accent-background-primary, 1), 2.5em 0em 0 0em rgba($accent-background-primary, 0.2), 1.75em 1.75em 0 0em rgba($accent-background-primary, 0.2), 0em 2.5em 0 0em rgba($accent-background-primary, 0.2), -1.8em 1.8em 0 0em rgba($accent-background-primary, 0.2), -2.6em 0em 0 0em rgba($accent-background-primary, 0.2), -1.8em -1.8em 0 0em rgba($accent-background-primary, 0.5); 49 | } 50 | 51 | 25% { 52 | box-shadow: 0em -2.6em 0em 0em rgba($accent-background-primary, 0.5), 1.8em -1.8em 0 0em rgba($accent-background-primary, 0.7), 2.5em 0em 0 0em rgba($accent-background-primary, 1), 1.75em 1.75em 0 0em rgba($accent-background-primary, 0.2), 0em 2.5em 0 0em rgba($accent-background-primary, 0.2), -1.8em 1.8em 0 0em rgba($accent-background-primary, 0.2), -2.6em 0em 0 0em rgba($accent-background-primary, 0.2), -1.8em -1.8em 0 0em rgba($accent-background-primary, 0.2); 53 | } 54 | 55 | 37.5% { 56 | box-shadow: 0em -2.6em 0em 0em rgba($accent-background-primary, 0.2), 1.8em -1.8em 0 0em rgba($accent-background-primary, 0.5), 2.5em 0em 0 0em rgba($accent-background-primary, 0.7), 1.75em 1.75em 0 0em rgba($accent-background-primary, 1), 0em 2.5em 0 0em rgba($accent-background-primary, 0.2), -1.8em 1.8em 0 0em rgba($accent-background-primary, 0.2), -2.6em 0em 0 0em rgba($accent-background-primary, 0.2), -1.8em -1.8em 0 0em rgba($accent-background-primary, 0.2); 57 | } 58 | 59 | 50% { 60 | box-shadow: 0em -2.6em 0em 0em rgba($accent-background-primary, 0.2), 1.8em -1.8em 0 0em rgba($accent-background-primary, 0.2), 2.5em 0em 0 0em rgba($accent-background-primary, 0.5), 1.75em 1.75em 0 0em rgba($accent-background-primary, 0.7), 0em 2.5em 0 0em rgba($accent-background-primary, 1), -1.8em 1.8em 0 0em rgba($accent-background-primary, 0.2), -2.6em 0em 0 0em rgba($accent-background-primary, 0.2), -1.8em -1.8em 0 0em rgba($accent-background-primary, 0.2); 61 | } 62 | 63 | 62.5% { 64 | box-shadow: 0em -2.6em 0em 0em rgba($accent-background-primary, 0.2), 1.8em -1.8em 0 0em rgba($accent-background-primary, 0.2), 2.5em 0em 0 0em rgba($accent-background-primary, 0.2), 1.75em 1.75em 0 0em rgba($accent-background-primary, 0.5), 0em 2.5em 0 0em rgba($accent-background-primary, 0.7), -1.8em 1.8em 0 0em rgba($accent-background-primary, 1), -2.6em 0em 0 0em rgba($accent-background-primary, 0.2), -1.8em -1.8em 0 0em rgba($accent-background-primary, 0.2); 65 | } 66 | 67 | 75% { 68 | box-shadow: 0em -2.6em 0em 0em rgba($accent-background-primary, 0.2), 1.8em -1.8em 0 0em rgba($accent-background-primary, 0.2), 2.5em 0em 0 0em rgba($accent-background-primary, 0.2), 1.75em 1.75em 0 0em rgba($accent-background-primary, 0.2), 0em 2.5em 0 0em rgba($accent-background-primary, 0.5), -1.8em 1.8em 0 0em rgba($accent-background-primary, 0.7), -2.6em 0em 0 0em rgba($accent-background-primary, 1), -1.8em -1.8em 0 0em rgba($accent-background-primary, 0.2); 69 | } 70 | 71 | 87.5% { 72 | box-shadow: 0em -2.6em 0em 0em rgba($accent-background-primary, 0.2), 1.8em -1.8em 0 0em rgba($accent-background-primary, 0.2), 2.5em 0em 0 0em rgba($accent-background-primary, 0.2), 1.75em 1.75em 0 0em rgba($accent-background-primary, 0.2), 0em 2.5em 0 0em rgba($accent-background-primary, 0.2), -1.8em 1.8em 0 0em rgba($accent-background-primary, 0.5), -2.6em 0em 0 0em rgba($accent-background-primary, 0.7), -1.8em -1.8em 0 0em rgba($accent-background-primary, 1); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/notification/async-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, OnDestroy, OnChanges, SimpleChanges, SimpleChange } from '@angular/core'; 2 | import { Observable, Subscription } from 'rxjs'; 3 | import { BaseComponent } from '../base.component'; 4 | import { Utilities } from '../../helpers'; 5 | import { NotificationService, MessageType } from '../../services'; 6 | import './async-view.component.scss'; 7 | 8 | @Component({ 9 | selector: 'async-view', 10 | templateUrl: 'async-view.component.html' 11 | }) 12 | export class AsyncViewComponent extends BaseComponent implements OnInit, OnDestroy, OnChanges { 13 | @Input() observable: any | Observable; 14 | @Input() placeholder: string; 15 | @Input() loading: string; 16 | @Input() error: string; 17 | isLoading: boolean; 18 | showError: boolean; 19 | data: any; 20 | 21 | constructor(private _notificationService: NotificationService) { 22 | super(); 23 | } 24 | 25 | ngOnInit() { 26 | this.isLoading = true; 27 | } 28 | 29 | ngOnChanges(changes: SimpleChanges) { 30 | var observable = changes['observable']; 31 | if (Utilities.isNull(observable) || Utilities.isNull(observable.currentValue)) return; 32 | if (observable.currentValue instanceof Observable) this._subscribe(); 33 | else if (observable.currentValue instanceof Promise) this._then(); 34 | else { 35 | this.data = observable.currentValue; 36 | this.isLoading = false; 37 | this.showError = false; 38 | } 39 | } 40 | 41 | private _subscribe() { 42 | var subscription = (this.observable as Observable).subscribe( 43 | next => { 44 | this.data = next; 45 | this.showError = false; 46 | }, 47 | error => { 48 | this.showError = true; 49 | this._notificationService.message(JSON.stringify(error), MessageType.Error); 50 | }, 51 | () => this.isLoading = false 52 | ) 53 | this.markDispose(subscription); 54 | } 55 | 56 | private _then() { 57 | (this.observable as Promise).then( 58 | result => { 59 | this.data = result; 60 | this.showError = false; 61 | this.isLoading = false; 62 | }, 63 | error => { 64 | this.showError = true; 65 | this.isLoading = false; 66 | this._notificationService.message(JSON.stringify(error), MessageType.Error); 67 | } 68 | ) 69 | } 70 | 71 | get isEmpty() { 72 | return Utilities.isEmpty(this.data); 73 | } 74 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/notification/message-bar.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixins.scss'; 2 | @import '../../../../assets/styles/theme.scss'; 3 | 4 | .mw-message-bar { 5 | width: 100%; 6 | 7 | &__content { 8 | @include inline-flex(); 9 | width: 100%; 10 | align-items: center; 11 | 12 | .ms-Icon-large { 13 | top: 0; 14 | } 15 | } 16 | 17 | &__message { 18 | @include ellipsis(); 19 | flex: 1 1 auto; 20 | width: calc(100vw - 70px); 21 | } 22 | 23 | &__icon { 24 | color: $foreground-primary; 25 | margin-right: $default-spacing; 26 | 27 | &:hover { 28 | cursor: pointer; 29 | color: $accent-background-primary; 30 | } 31 | } 32 | 33 | &__actions { 34 | @include flex(); 35 | padding: $default-spacing; 36 | 37 | &--row { 38 | @include inline-flex(); 39 | margin-top: 7px; 40 | } 41 | } 42 | 43 | &__action { 44 | border: solid 1px $foreground-primary; 45 | outline: none; 46 | color: $foreground-primary; 47 | text-align: center; 48 | @include focus-states($foreground-primary, $background-light); 49 | margin-bottom: $default-spacing/2; 50 | flex: 1 1 50%; 51 | @include ellipsis(); 52 | 53 | &--primary { 54 | background-color: $accent-background-primary; 55 | color: $accent-foreground-primary; 56 | border-color: $accent-background-primary; 57 | } 58 | 59 | &--secondary { 60 | background-color: $foreground-white; 61 | color: $foreground-primary; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/notification/message-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from '@angular/core'; 2 | import { BaseComponent } from '../base.component'; 3 | import { MediatorService, IEventChannel, IMessage, MessageType } from '../../services'; 4 | import './message-bar.component.scss'; 5 | 6 | @Component({ 7 | selector: 'message-bar', 8 | template: ` 9 |
10 |
11 |
12 | 13 |
14 |
15 |

{{message?.message}}

16 |
17 |
18 | 19 |
20 |
21 |
22 | 23 | 24 |
25 |
` 26 | }) 27 | export class MessageBarComponent extends BaseComponent implements OnDestroy { 28 | message: IMessage; 29 | isHidden = true; 30 | 31 | variant: { 32 | class: string; 33 | icon: string; 34 | }; 35 | 36 | private _messageChannel: IEventChannel; 37 | 38 | constructor(private _mediatorService: MediatorService) { 39 | super(); 40 | this._messageChannel = this._mediatorService.createEventChannel('message-channel'); 41 | var subscription = this._messageChannel.source$.debounceTime(300).subscribe(message => this.showMessage(message)); 42 | this.markDispose(subscription); 43 | } 44 | 45 | dismiss() { 46 | if (this.message.action && this.message.action.dismissEvent) { 47 | this.message.action.dismissEvent.next(null); 48 | } 49 | this.message = null; 50 | this.isHidden = true; 51 | } 52 | 53 | showMessage(message: IMessage) { 54 | this.message = message; 55 | this._determineVariant(message.type); 56 | this.isHidden = false; 57 | } 58 | 59 | yes() { 60 | if (this.message.action && this.message.action.actionEvent) { 61 | this.message.action.actionEvent.next(true); 62 | this.dismiss(); 63 | } 64 | } 65 | 66 | no() { 67 | if (this.message.action && this.message.action.actionEvent) { 68 | this.message.action.actionEvent.next(false); 69 | this.dismiss(); 70 | } 71 | } 72 | 73 | private _determineVariant(type: MessageType) { 74 | switch (type) { 75 | case MessageType.Error: 76 | this.variant = { class: "ms-MessageBar--error", icon: "ms-Icon--xCircle" }; 77 | break; 78 | 79 | case MessageType.Remove: 80 | this.variant = { class: "ms-MessageBar--remove", icon: "ms-Icon--minus ms-Icon--circle" }; 81 | break; 82 | 83 | case MessageType.SevereWarning: 84 | this.variant = { class: "ms-MessageBar--severeWarning", icon: "ms-Icon--alert" }; 85 | break; 86 | 87 | case MessageType.Success: 88 | this.variant = { class: "ms-MessageBar--success", icon: "ms-Icon--checkboxCheck ms-Icon--circle" }; 89 | break; 90 | 91 | case MessageType.Warning: 92 | this.variant = { class: "ms-MessageBar--warning", icon: "ms-Icon--infoCircle" }; 93 | break; 94 | 95 | case MessageType.Info: 96 | default: 97 | this.variant = { class: "", icon: "ms-Icon--infoCircle" }; 98 | break; 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/notification/toast.component.scss: -------------------------------------------------------------------------------- 1 | /* Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license in root of repo. */ 2 | .ms-toast { 3 | padding: 10px; 4 | box-shadow: 0 0 5px 1px rgba(0, 0, 0, 0.4); 5 | position: absolute; 6 | z-index: 999; 7 | bottom: 15px; 8 | left: 15px; 9 | width: calc(100% - 50px); 10 | opacity: 0; 11 | background-color: #FFF; 12 | transition: opacity ease-in-out 0.1s; 13 | display: -webkit-flex; 14 | display: flex; 15 | -webkit-flex-direction: column; 16 | flex-direction: column; 17 | -webkit-flex-wrap: nowrap; 18 | flex-wrap: nowrap; } 19 | 20 | .ms-toast--shown { 21 | opacity: 1; 22 | } 23 | 24 | .ms-toast__content, .ms-toast__header { 25 | display: -webkit-inline-flex; 26 | display: inline-flex; 27 | -webkit-justify-content: flex-start; 28 | justify-content: flex-start; 29 | -webkit-align-items: flex-start; 30 | align-items: flex-start; } 31 | .ms-toast__content h4, .ms-toast__header h4 { 32 | -webkit-flex: 1 0 0px; 33 | flex: 1 0 0px; 34 | margin: 0; 35 | padding: 0; 36 | width: auto; 37 | max-width: auto; 38 | overflow: hidden; 39 | white-space: nowrap; 40 | text-overflow: ellipsis; } 41 | .ms-toast__content .ms-Icon, .ms-toast__header .ms-Icon { 42 | top: 0; 43 | transition: background ease 0.1s, color ease 0.1s; } 44 | .ms-toast__content .ms-Icon.ms-Icon--x, .ms-toast__header .ms-Icon.ms-Icon--x { 45 | color: #666; position: absolute; right: 15px; top: 15px; } 46 | .ms-toast__content .ms-Icon:active, .ms-toast__content .ms-Icon:hover, .ms-toast__header .ms-Icon:active, .ms-toast__header .ms-Icon:hover { 47 | background: transparent; 48 | color: #0078d7; 49 | cursor: pointer; } 50 | .ms-toast__content .ms-Icon:active, .ms-toast__header .ms-Icon:active { 51 | background: transparent; 52 | -webkit-transform: scale3d(0.98, 0.98, 1); 53 | transform: scale3d(0.98, 0.98, 1); } 54 | .ms-toast__content .ms-Icon--disabled, .ms-toast__header .ms-Icon--disabled { 55 | opacity: 0.6; 56 | pointer-events: none; 57 | cursor: not-allowed; } 58 | .ms-toast__content .ms-Icon--disabled:active, .ms-toast__content .ms-Icon--disabled:hover, .ms-toast__header .ms-Icon--disabled:active, .ms-toast__header .ms-Icon--disabled:hover { 59 | background: transparent; } 60 | .ms-toast__content .ms-Icon--disabled:active, .ms-toast__header .ms-Icon--disabled:active { 61 | -webkit-transform: none; 62 | transform: none; } 63 | .ms-toast__content p, .ms-toast__header p { 64 | margin-left: 7px; } 65 | .ms-toast__content { 66 | margin-top: 7px; } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/components/notification/toast.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, ApplicationRef } from '@angular/core'; 2 | import { BaseComponent } from '../base.component'; 3 | import { MediatorService, IEventChannel, IToast } from '../../services'; 4 | import './toast.component.scss'; 5 | 6 | @Component({ 7 | selector: 'toast', 8 | template: ` 9 |
10 |
11 |

{{toast?.title}}

12 | 13 |
14 |
15 | 16 |

{{toast?.toast}}

17 |
18 |
` 19 | }) 20 | export class ToastComponent extends BaseComponent implements OnDestroy { 21 | toast: IToast; 22 | isHidden = true; 23 | 24 | private _timeout; 25 | private _toastChannel: IEventChannel; 26 | 27 | constructor( 28 | private _mediatorService: MediatorService, 29 | private _ref: ApplicationRef 30 | ) { 31 | super(); 32 | this._toastChannel = this._mediatorService.createEventChannel('toast-channel'); 33 | var subscription = this._toastChannel.source$.debounceTime(300).subscribe(toast => this.showToast(toast)); 34 | this.markDispose(subscription); 35 | } 36 | 37 | showToast(toast: IToast) { 38 | this.toast = toast; 39 | if (this._timeout) clearTimeout(this._timeout); 40 | this.isHidden = false; 41 | this._ref.tick(); 42 | // setTimeout(() => this.dismiss(), 3000); 43 | } 44 | 45 | dismiss() { 46 | this.toast = null; 47 | this.isHidden = true; 48 | clearTimeout(this._timeout); 49 | } 50 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/helpers/exception.helper.ts: -------------------------------------------------------------------------------- 1 | import { ErrorHandler } from '@angular/core'; 2 | 3 | export class ExceptionHelper implements ErrorHandler { 4 | handleError(exception: any) { 5 | console.group(exception.description || 'Handled Exception'); 6 | console.error(exception); 7 | console.groupEnd(); 8 | } 9 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { ExceptionHelper } from './exception.helper'; 2 | import { RequestHelper } from './request.helper'; 3 | import { Utilities } from './utilities'; 4 | 5 | const HELPERS = [ 6 | ExceptionHelper, 7 | RequestHelper 8 | ]; 9 | 10 | export { ExceptionHelper, RequestHelper, Utilities, HELPERS }; -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/helpers/request.helper.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Headers, RequestOptions } from '@angular/http'; 3 | import { IToken } from '@microsoft/office-js-helpers'; 4 | import { Observable } from 'rxjs/Rx'; 5 | import { Utilities } from './'; 6 | 7 | @Injectable() 8 | export class RequestHelper { 9 | private _token: IToken; 10 | 11 | constructor(private _http: Http) { } 12 | 13 | get(url: string, options?: RequestOptions, unformatted?: boolean) { 14 | let requestOptions = options || this._generateHeaders(); 15 | let xhr = Utilities.isNull(requestOptions) ? this._http.get(url) : this._http.get(url, requestOptions); 16 | return unformatted ? xhr : this._json(xhr); 17 | } 18 | 19 | getWithMediaHeaders(url: string, options?: RequestOptions, unformatted?: boolean) { 20 | let requestOptions = options || this._generateWithMediaHeaders(); 21 | let xhr = Utilities.isNull(requestOptions) ? this._http.get(url) : this._http.get(url, requestOptions); 22 | return unformatted ? xhr : this._text(xhr); 23 | } 24 | 25 | put(url: string, body: any, options?: RequestOptions, unformatted?: boolean) { 26 | let requestOptions = options || this._generateHeaders(); 27 | let xhr = Utilities.isNull(requestOptions) ? this._http.put(url, JSON.stringify(body)) : this._http.put(url, JSON.stringify(body), requestOptions); 28 | return unformatted ? xhr : this._json(xhr); 29 | } 30 | 31 | raw(url: string, options?: RequestOptions, unformatted?: boolean) { 32 | let xhr = Utilities.isNull(options) ? this._http.get(url) : this._http.get(url, options); 33 | return unformatted ? xhr : this._text(xhr); 34 | } 35 | 36 | token(value: IToken): IToken { 37 | if (Utilities.isNull(value)) { 38 | throw new Error('Token cannot be null!'); 39 | } 40 | 41 | this._token = value; 42 | return this._token; 43 | } 44 | 45 | private _generateHeaders(): RequestOptions { 46 | if (Utilities.isNull(this._token)) { 47 | throw new Error('Token is null! Please authenticate first.'); 48 | } 49 | 50 | var headers = new Headers({ 51 | "Accept": "application/json", 52 | "Authorization": "Bearer " + this._token.access_token 53 | }); 54 | 55 | return new RequestOptions({ headers: headers }); 56 | } 57 | 58 | private _generateWithMediaHeaders(): RequestOptions { 59 | if (Utilities.isNull(this._token)) { 60 | throw new Error('Token is null! Please authenticate first.'); 61 | } 62 | 63 | var headers = new Headers({ 64 | "Accept": "application/vnd.github.VERSION.html", 65 | "Authorization": "Bearer " + this._token.access_token 66 | }); 67 | 68 | return new RequestOptions({ headers: headers }); 69 | } 70 | 71 | private _text(request: Observable): Observable { 72 | return request 73 | .map(response => response.text() as string) 74 | .catch((err: any, caught: Observable) => Utilities.error(err) as Observable); 75 | } 76 | 77 | private _json(request: Observable): Observable { 78 | return request 79 | .map(response => response.json() as T) 80 | .catch((err: any, caught: Observable) => Utilities.error(err) as Observable); 81 | } 82 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/helpers/utilities.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Rx'; 2 | 3 | export enum ContextType { 4 | Web, 5 | Word 6 | } 7 | 8 | export class Utilities { 9 | private static _context: ContextType; 10 | 11 | static replace(source: string): (key: string, value: string) => any { 12 | return function self(key: string, value: string): any { 13 | if (!key) return source; 14 | source = source.replace(key, value); 15 | return self; 16 | }; 17 | } 18 | 19 | static regex(source: string): (key: RegExp, value: string) => any { 20 | return function self(key: RegExp, value: string): any { 21 | if (!key) return source; 22 | source = source.replace(key, value); 23 | return self; 24 | }; 25 | } 26 | 27 | static isNull(obj: any): boolean { 28 | return _.isUndefined(obj) || _.isNull(obj); 29 | } 30 | 31 | static isEmpty(obj: any): boolean { 32 | return Utilities.isNull(obj) || _.isEmpty(obj); 33 | } 34 | 35 | static get isWord() { 36 | return this._context == ContextType.Word; 37 | } 38 | 39 | static get isWeb() { 40 | return this._context == ContextType.Web; 41 | } 42 | 43 | static error(exception?: any): Observable | Promise | OfficeExtension.IPromise { 44 | appInsights.trackException(exception); 45 | 46 | console.group(`Error: ${exception.message}`); 47 | if (exception.stack) { 48 | console.groupCollapsed('Stack Trace'); 49 | console.error(exception.stack); 50 | console.groupEnd(); 51 | } 52 | if (Utilities.isWord) { 53 | console.groupCollapsed('Office Exception'); 54 | if (exception instanceof OfficeExtension.Error) { 55 | console.error('Debug info: ' + JSON.stringify(exception.debugInfo)); 56 | } 57 | console.groupEnd(); 58 | } 59 | 60 | console.groupEnd(); 61 | return exception; 62 | } 63 | 64 | static setContext() { 65 | if (_.has(window, 'Office')) { 66 | if (_.has(window, 'Word')) { 67 | this._context = ContextType.Word; 68 | } 69 | } 70 | else { 71 | this._context = ContextType.Web; 72 | }; 73 | } 74 | } 75 | 76 | Utilities.setContext(); -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/pipes/index.ts: -------------------------------------------------------------------------------- 1 | import { SafeNamesPipe } from './safenames.pipe'; 2 | import { MDFilterPipe } from './md-filter.pipe'; 3 | 4 | const PIPES = [ 5 | SafeNamesPipe, 6 | MDFilterPipe 7 | ]; 8 | 9 | export { SafeNamesPipe, MDFilterPipe, PIPES }; -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/pipes/md-filter.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { Utilities } from '../helpers'; 3 | import { IContents } from '../services'; 4 | 5 | @Pipe({ 6 | name: 'mdfilter' 7 | }) 8 | 9 | export class MDFilterPipe implements PipeTransform { 10 | transform(array: IContents[]) { 11 | if (Utilities.isEmpty(array)) return array; 12 | 13 | return array.filter(item => { 14 | var nameRegex = /^[^.]+\.md$/gm; 15 | if (item.type == 'dir') return true; 16 | else if (nameRegex.test(item.name)) return true; 17 | }); 18 | } 19 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/pipes/safenames.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { Utilities } from '../helpers'; 3 | 4 | @Pipe({ 5 | name: 'safenames' 6 | }) 7 | 8 | export class SafeNamesPipe implements PipeTransform { 9 | transform(name: string, dashed: boolean) { 10 | if (Utilities.isEmpty(name)) return name; 11 | 12 | if (!!!dashed) { 13 | return name.replace(/-/g, ' '); 14 | } 15 | else { 16 | return name.replace(/\s/g, '-'); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/services/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, EventEmitter, OnDestroy } from '@angular/core'; 2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3 | import { Observable } from 'rxjs/Rx'; 4 | import { Utilities } from '../helpers'; 5 | import { GithubService } from './github.service'; 6 | 7 | @Injectable() 8 | export class AuthGuard implements CanActivate { 9 | constructor( 10 | private _router: Router, 11 | private _githubService: GithubService 12 | ) { 13 | 14 | } 15 | 16 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 17 | if (!Utilities.isNull(this._githubService.profile)) { return true; } 18 | this._router.navigate(['/login']); 19 | return false; 20 | } 21 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/services/favorites.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, EventEmitter } from '@angular/core'; 2 | import { Storage } from '@microsoft/office-js-helpers'; 3 | import { Utilities } from '../helpers'; 4 | import { Observable, Observer, Subscription } from 'rxjs/Rx'; 5 | import { IRepository } from './models'; 6 | import { MediatorService, IEventChannel } from './mediator.service'; 7 | 8 | @Injectable() 9 | export class FavoritesService extends Storage { 10 | pushDataEvent: IEventChannel; 11 | cache: Storage; 12 | sub: Subscription; 13 | 14 | constructor(private _mediatorService: MediatorService) { 15 | super("FavoriteRepositories"); 16 | this.pushDataEvent = this._mediatorService.createEventChannel('favorites'); 17 | } 18 | 19 | pin(item: IRepository) { 20 | this.insert(item.id.toString(), item); 21 | this.pushDataEvent.source.next(item); 22 | } 23 | 24 | unpin(item: IRepository) { 25 | this.remove(item.id.toString()); 26 | this.pushDataEvent.source.next(item); 27 | } 28 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/services/github.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, Observer } from 'rxjs/Rx'; 3 | import { Authenticator, Storage, IToken } from '@microsoft/office-js-helpers'; 4 | import { Utilities, RequestHelper } from '../helpers'; 5 | import { IRepository, IBranch, IContents, IProfileMetadata, IUserProfile, ICommit, IUploadCommit } from './'; 6 | 7 | declare var Microsoft: any; 8 | 9 | @Injectable() 10 | export class GithubService { 11 | private _baseUrl: string = ""; 12 | private _profile: IUserProfile; 13 | private _profileStorage: Storage; 14 | private _authenticator: Authenticator; 15 | 16 | constructor(private _request: RequestHelper) { 17 | this._profileStorage = new Storage('Profile'); 18 | this._authenticator = new Authenticator(); 19 | this._authenticator.endpoints.add('GitHub', { 20 | clientId: '61ef07373b60f4f075cd', 21 | baseUrl: 'https://github.com/login', 22 | authorizeUrl: '/oauth/authorize', 23 | tokenUrl: 'https://markdowneditorforwordauth.azurewebsites.net/api/dev?code=6b3ox717lvxoltr4whgruanhfrqppnjuf35r3geoorsqrm5cdid91xuwuifqhj7g24q9rafw29', 24 | scope: 'repo', 25 | state: true 26 | }); 27 | } 28 | 29 | user(): Observable { 30 | return this._request.get("https://api.github.com/user") as Observable; 31 | } 32 | 33 | orgs(username: string): Observable { 34 | return this._request.get("https://api.github.com/users/" + username + "/orgs") as Observable; 35 | } 36 | 37 | repos(page: number, orgName: string, personal: boolean): Observable { 38 | var url = personal ? "https://api.github.com/user/repos?page=" + page + "&affiliation=owner,collaborator&sort=updated&direction=desc" : "https://api.github.com/orgs/" + orgName + "/repos?page=" + page; 39 | return this._request.get(url) as Observable; 40 | } 41 | 42 | files(orgName: string, repoName: string, branchName: string, path?: string): Observable { 43 | var url = "https://api.github.com/repos/" + orgName + "/" + repoName + "/contents"; 44 | if (!Utilities.isNull(path)) { url += "/" + path; } 45 | return this._request.get(url + "?ref=" + branchName) as Observable; 46 | } 47 | 48 | branches(orgName: string, repoName: string): Observable { 49 | return this._request.get("https://api.github.com/repos/" + orgName + "/" + repoName + "/branches") as Observable; 50 | } 51 | 52 | file(orgName: string, repoName: string, branchName: string, filePath: string): Observable { 53 | return this._request.getWithMediaHeaders("https://api.github.com/repos/" + orgName + "/" + repoName + "/contents/" + filePath + "?ref=" + branchName) as Observable; 54 | } 55 | 56 | commits(orgName: string, repoName: string, branchName: string, filePath: string): Observable { 57 | return this._request.get("https://api.github.com/repos/" + orgName + "/" + repoName + "/commits?path=" + filePath + "&sha=" + branchName + "&until=" + (new Date().toISOString())) as Observable; 58 | } 59 | 60 | getSha(orgName: string, repoName: string, branchName: string, path?: string): Observable { 61 | var url = "https://api.github.com/repos/" + orgName + "/" + repoName + "/contents"; 62 | if (!Utilities.isNull(path)) { url += "/" + path; } 63 | return this._request.get(url + "?ref=" + branchName) as Observable; 64 | } 65 | 66 | createFile(orgName: string, repoName: string, filePath: string, body: any): Observable { 67 | return this._request.put("https://api.github.com/repos/" + orgName + "/" + repoName + "/contents/" + filePath, body) as Observable; 68 | } 69 | 70 | updateFile(orgName: string, repoName: string, filePath: string, body: any): Observable { 71 | return this._request.put("https://api.github.com/repos/" + orgName + "/" + repoName + "/contents/" + filePath, body) as Observable; 72 | } 73 | 74 | uploadImage(orgName: string, repoName: string, fileName: string, body: any): Observable { 75 | return this._request.put("https://api.github.com/repos/" + orgName + "/" + repoName + "/contents/" + fileName, body) as Observable; 76 | } 77 | 78 | getFileData(filename: string): Observable { 79 | if (filename == null) return Observable.of(''); 80 | return this._request.raw('assets/templates/' + filename + '.md') as Observable; 81 | } 82 | 83 | login(): Promise { 84 | return this._authenticator.authenticate('GitHub') 85 | .then(token => this._getProfile(token)) 86 | .then(profile => this.profile = profile); 87 | } 88 | 89 | logout() { 90 | Storage.clearAll(); 91 | } 92 | 93 | get profile(): IUserProfile { 94 | if (Utilities.isEmpty(this._profile)) { 95 | this._profile = this._profileStorage.values()[0]; 96 | 97 | if (!Utilities.isEmpty(this._profile)) { 98 | this._request.token(this._profile.token); 99 | } 100 | } 101 | 102 | return this._profile; 103 | } 104 | 105 | set profile(value: IUserProfile) { 106 | if (!Utilities.isEmpty(value)) { 107 | this._profile = value; 108 | this._profileStorage.insert(value.user.login, value); 109 | } 110 | } 111 | 112 | private _getProfile(token: IToken) { 113 | var _userMetadata: IProfileMetadata; 114 | this._request.token(token); 115 | return this.user().toPromise() 116 | .then(userMetadata => { 117 | _userMetadata = userMetadata; 118 | return this.orgs(_userMetadata.login).toPromise(); 119 | }) 120 | .then(orgs => { 121 | return { 122 | token: token, 123 | orgs: orgs, 124 | user: _userMetadata 125 | }; 126 | }) 127 | } 128 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/services/index.ts: -------------------------------------------------------------------------------- 1 | import { GithubService } from './github.service'; 2 | import { MarkdownService } from './markdown.service'; 3 | import { WordService } from './word.service'; 4 | import { MediatorService } from './mediator.service'; 5 | import { FavoritesService } from './favorites.service'; 6 | import { AuthGuard } from './auth.guard'; 7 | import { NotificationService } from './notification.service'; 8 | 9 | export * from './github.service'; 10 | export * from './markdown.service'; 11 | export * from './word.service'; 12 | export * from './mediator.service'; 13 | export * from './models'; 14 | export * from './favorites.service'; 15 | export * from './auth.guard'; 16 | export * from './notification.service'; 17 | 18 | export const SERVICES = [ 19 | GithubService, 20 | MarkdownService, 21 | WordService, 22 | MediatorService, 23 | FavoritesService, 24 | AuthGuard, 25 | NotificationService 26 | ]; -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/app/shared/services/markdown.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, Observer } from 'rxjs/Rx' 3 | import { Storage } from '@microsoft/office-js-helpers'; 4 | import * as marked from 'marked'; 5 | import { Utilities } from '../helpers'; 6 | 7 | declare var toMarkdown: any; 8 | declare var Microsoft: any; 9 | 10 | @Injectable() 11 | export class MarkdownService { 12 | private _storage: Storage; 13 | 14 | constructor() { 15 | this._storage = new Storage('MarkdownPreview'); 16 | } 17 | 18 | convertToHtml(markdown: string) { 19 | return marked(markdown); 20 | } 21 | 22 | convertToMD(html: string) { 23 | appInsights.trackEvent('convert html to md'); 24 | var start = performance.now(); 25 | 26 | var wrappingElement = document.createElement('div'); 27 | wrappingElement.classList.add('highlight') 28 | wrappingElement.classList.add('highlight-javascript'); 29 | 30 | var extractedHtml = $(`
${this._cleanHtml(html)}
`).find('.WordSection1'); 31 | extractedHtml.children('pre').wrap(wrappingElement); 32 | 33 | extractedHtml.find('td').each((index, elem) => { 34 | var content = ''; 35 | var td = $(elem); 36 | 37 | var ps = td.children('p'); 38 | if (ps.length == 1) { 39 | content = ps.text(); 40 | ps.remove(); 41 | } 42 | else { 43 | ps.each((index, p) => { 44 | content += $(p).text(); 45 | $(p).remove(); 46 | }); 47 | } 48 | 49 | td.text(content); 50 | }); 51 | 52 | var md = toMarkdown(extractedHtml.html(), { 53 | gfm: true, 54 | converters: [{ 55 | filter: 'li', 56 | replacement: function (content, node) { 57 | content = content.replace(/^\s+/, '').replace(/\n/gm, '\n ') 58 | var prefix = '* ' 59 | var parent = node.parentNode 60 | var grandparent = parent.parentNode 61 | var index = Array.prototype.indexOf.call(parent.children, node) + 1 62 | 63 | if (/ol/i.test(grandparent.nodeName) || /ul/i.test(grandparent.nodeName)) { 64 | prefix = /ol/i.test(parent.nodeName) ? ' ' + index + '. ' : ' * ' 65 | } 66 | else { 67 | prefix = /ol/i.test(parent.nodeName) ? index + '. ' : '* ' 68 | } 69 | 70 | return prefix + content 71 | } 72 | }] 73 | }); 74 | 75 | var end = performance.now(); 76 | appInsights.trackMetric("markdown conversion duration", (end - start) / 1000); 77 | return md; 78 | } 79 | 80 | private _cleanHtml(html: string): string { 81 | return Utilities.regex(html) 82 | 83 | // clean the TABLE, TD, P or SPAN tags by removing any additional properties added by Word 84 | // this step is necessary as it optimizes the regex for further steps 85 | // else we end up in catastrophic backtracking http://www.regular-expressions.info/catastrophic.html 86 | (/<(table|td|p|span)\s(?:.|\s)*?>/g, '<$1>') 87 | 88 | // wrap the first TR in a table with THEAD tag and start a TBODY tag at the end of it 89 | (/((?:.|\s)*?)<\/tr>/g, '\n\n$1\n\n\n') 90 | 91 | // end the ending TABLE tag with TBODY 92 | (/<\/table>/, '\n
\n') 93 | 94 | // replace all span with their contents 95 | (/<(?:\/|)span>/g, '') 96 | 97 | // replace all var with blockquotes 98 | (/<(\/|)var>/g, '<$1blockquote>') 99 | 100 | // replace all span with their contents 101 | (/<\/pre>
/g, '\n')
102 | 
103 |             // generate output
104 |             ();
105 |     }
106 | }


--------------------------------------------------------------------------------
/MarkdownEditorForWord/src/app/shared/services/mediator.service.ts:
--------------------------------------------------------------------------------
 1 | import { Injectable, EventEmitter, OnDestroy } from '@angular/core';
 2 | import { Subject, Observable } from 'rxjs/Rx';
 3 | import { Authenticator, Dictionary, IToken } from '@microsoft/office-js-helpers';
 4 | import { Utilities } from '../helpers';
 5 | 
 6 | export interface IChannel {
 7 |     name: string,
 8 |     source$: Observable
 9 | }
10 | 
11 | export interface IEventChannel extends IChannel {
12 |     source: EventEmitter
13 | }
14 | 
15 | export interface ISubjectChannel extends IChannel {
16 |     source: Subject
17 | }
18 | 
19 | @Injectable()
20 | export class MediatorService extends Dictionary implements OnDestroy {
21 |     constructor() {
22 |         super();
23 |     }
24 | 
25 |     createEventChannel(name: string): IEventChannel {
26 |         var current = this.get(name);
27 |         if (!Utilities.isNull(current)) return current as IEventChannel;
28 | 
29 |         var event = new EventEmitter();
30 |         return this.insert(name, { name: name, source$: event.asObservable(), source: event } as IChannel) as IEventChannel;
31 |     }
32 | 
33 |     createSubjectChannel(name: string): ISubjectChannel {
34 |         var current = this.get(name);
35 |         if (!Utilities.isNull(current)) return current as ISubjectChannel;
36 | 
37 |         var dataSource = new Subject();
38 |         var event = dataSource.asObservable();
39 |         return this.insert(name, { name: name, source$: event, source: dataSource } as IChannel) as ISubjectChannel;
40 |     }
41 | 
42 |     clear() {
43 |         _.each(this.values(), subscription => {
44 |             ((subscription)).source.unsubscribe();
45 |         });
46 | 
47 |         super.clear();
48 |     }
49 | 
50 |     ngOnDestroy() {
51 |         this.clear();
52 |     }
53 | }


--------------------------------------------------------------------------------
/MarkdownEditorForWord/src/app/shared/services/models.ts:
--------------------------------------------------------------------------------
  1 | import { IToken } from '@microsoft/office-js-helpers';
  2 | 
  3 | export interface IProfile {
  4 |     login?: string,
  5 |     id?: number,
  6 |     avatar_url?: string,
  7 |     gravatar_id?: string,
  8 |     html_url?: string,
  9 |     url?: string,
 10 |     type?: string,
 11 |     site_admin?: boolean
 12 | }
 13 | 
 14 | export interface IProfileMetadata extends IProfile {
 15 |     name?: string,
 16 |     company?: string,
 17 |     blog?: string,
 18 |     location?: string,
 19 |     email?: string,
 20 |     hireable?: string,
 21 |     bio?: string,
 22 |     public_repos?: number,
 23 |     public_gists?: number,
 24 |     followers?: number,
 25 |     following?: number,
 26 |     created_at?: Date,
 27 |     updated_at?: Date
 28 | }
 29 | 
 30 | export interface IUserProfile {
 31 |     user: IProfileMetadata,
 32 |     orgs: IProfileMetadata[],
 33 |     token: IToken
 34 | }
 35 | 
 36 | export interface IRepository {
 37 |     id?: number,
 38 |     name?: string,
 39 |     full_name?: string,
 40 |     owner: IProfile,
 41 |     private?: boolean,
 42 |     html_url?: string,
 43 |     description?: string,
 44 |     fork?: boolean,
 45 |     url?: string,
 46 |     created_at?: Date,
 47 |     updated_at?: Date,
 48 |     pushed_at?: Date,
 49 |     homepage?: string
 50 |     size?: number,
 51 |     stargazers_count?: number,
 52 |     watchers_count?: number,
 53 |     language?: string,
 54 |     has_issues?: boolean,
 55 |     has_downloads?: boolean,
 56 |     has_wiki?: boolean,
 57 |     has_pages?: boolean,
 58 |     forks_count?: number,
 59 |     open_issues_count?: number,
 60 |     forks?: number,
 61 |     open_issues?: number,
 62 |     watchers?: number,
 63 |     default_branch?: string,
 64 |     permissions?: {
 65 |         admin?: boolean,
 66 |         push?: boolean,
 67 |         pull: boolean
 68 |     },
 69 |     isPinned?: boolean
 70 | }
 71 | 
 72 | export interface IRepositoryCollection {
 73 |     data: IRepository[],
 74 |     page_count?: number,
 75 |     next_link?: string
 76 | }
 77 | 
 78 | export interface IOwnerRepository extends IRepository {
 79 |     organization?: IProfile,
 80 |     parent?: IRepository,
 81 |     source?: IRepository
 82 | }
 83 | 
 84 | export interface IBranch {
 85 |     name?: string,
 86 |     protection?: {
 87 |         enabled?: boolean,
 88 |         required_status_checks?: {
 89 |             enforcement_level?: string,
 90 |             contexts: any[]
 91 |         }
 92 |     },
 93 |     commit?: ICommit,
 94 |     _links?: {
 95 |         html?: string,
 96 |         self?: string
 97 |     }
 98 | }
 99 | 
100 | export interface ICommit {
101 |     sha?: string,
102 |     commit?: {
103 |         author?: {
104 |             name?: string,
105 |             date?: Date,
106 |             email?: string
107 |         },
108 |         url?: string,
109 |         message?: string,
110 |         tree?: {
111 |             sha?: string,
112 |             url?: string
113 |         },
114 |         committer?: {
115 |             name?: string,
116 |             date?: Date,
117 |             email?: string
118 |         }
119 |     },
120 |     author?: {
121 |         gravatar_id?: string,
122 |         avatar_url?: string,
123 |         url?: string,
124 |         id?: number,
125 |         login?: string
126 |     },
127 |     parents?: [
128 |         {
129 |             sha?: string,
130 |             url?: string
131 |         },
132 |         {
133 |             sha?: string,
134 |             url?: string
135 |         }
136 |     ],
137 |     url?: string,
138 |     committer?: {
139 |         gravatar_id?: string,
140 |         avatar_url?: string,
141 |         url?: string,
142 |         id?: number,
143 |         login?: string
144 |     }
145 | }
146 | 
147 | export interface IContents {
148 |     type?: string,
149 |     size?: number,
150 |     name?: string,
151 |     path?: string,
152 |     sha?: string,
153 |     url?: string,
154 |     git_url?: string,
155 |     html_url?: string,
156 |     content?: string,
157 |     download_url?: string,
158 |     _links?: {
159 |         self?: string,
160 |         git?: string,
161 |         html?: string
162 |     }
163 | }
164 | 
165 | export interface IUploadCommit {
166 |     commit: ICommit,
167 |     content: IContents
168 | }
169 | 
170 | export interface IBreadcrumb {
171 |     text: string;
172 |     key?: number;
173 |     href?: string | string[];
174 | }
175 | 
176 | export interface IImage {
177 |     id?: number,
178 |     altTextTitle?: string,
179 |     altTextDescription?: string,
180 |     height?: number,
181 |     hyperlink?: string,
182 |     imageFormat?: string,
183 |     lockAspectRatio?: boolean,
184 |     width?: number,
185 |     base64ImageSrc?: OfficeExtension.ClientResult
186 | }


--------------------------------------------------------------------------------
/MarkdownEditorForWord/src/app/shared/services/notification.service.ts:
--------------------------------------------------------------------------------
 1 | import { Injectable, EventEmitter } from '@angular/core'
 2 | import { Utilities } from '../helpers';
 3 | import { Observable } from 'rxjs/Rx';
 4 | import { MediatorService, IEventChannel } from './mediator.service';
 5 | 
 6 | export enum MessageType {
 7 |     Info,
 8 |     Success,
 9 |     SevereWarning,
10 |     Warning,
11 |     Error,
12 |     Remove
13 | }
14 | 
15 | export interface IMessage {
16 |     message: string;
17 |     type: MessageType;
18 |     action?: MessageAction;
19 | }
20 | 
21 | export interface IToast {
22 |     toast: string;
23 |     title: string;
24 | }
25 | 
26 | export class MessageAction {
27 |     actionEvent: EventEmitter
28 |     dismissEvent: EventEmitter
29 | 
30 |     constructor(public yes: string, public no?: string) {
31 |         this.actionEvent = new EventEmitter();
32 |         this.dismissEvent = new EventEmitter();
33 |     }
34 | }
35 | 
36 | @Injectable()
37 | export class NotificationService {
38 |     private _toastChannel: IEventChannel;
39 |     private _messageChannel: IEventChannel;
40 | 
41 |     constructor(private _mediatorService: MediatorService) {
42 |         this._toastChannel = this._mediatorService.createEventChannel('toast-channel');
43 |         this._messageChannel = this._mediatorService.createEventChannel('message-channel');
44 |     }
45 | 
46 |     message(message: string, type?: MessageType)
47 |     message(message: IMessage)
48 |     message(message: any, type?: any) {
49 |         if (Utilities.isEmpty(message)) return;
50 |         if (_.isString(message)) {
51 |             this._messageChannel.source.next({
52 |                 message: message,
53 |                 type: type
54 |             });
55 |         }
56 |         else this._messageChannel.source.next(message);
57 |     }
58 | 
59 |     error(message: string)
60 |     error(message: any) {
61 |         if (_.isString(message)) this.message(message, MessageType.Error);
62 |         else this.message(JSON.stringify(message), MessageType.Error);
63 |         Utilities.error(JSON.stringify(message));
64 |         appInsights.trackException(message);
65 |     }
66 | 
67 |     toast(toast: IToast)
68 |     toast(toast: string, title?: string)
69 |     toast(toast: any, title?: any) {
70 |         if (Utilities.isEmpty(toast)) return;
71 |         if (_.isString(toast)) {
72 |             this._toastChannel.source.next({
73 |                 toast: toast,
74 |                 title: title
75 |             });
76 |         }
77 |         else this._toastChannel.source.next(toast);
78 |     }
79 | }
80 | 


--------------------------------------------------------------------------------
/MarkdownEditorForWord/src/app/shared/services/word.service.ts:
--------------------------------------------------------------------------------
  1 | import { Inject, Injectable } from "@angular/core";
  2 | import { GithubService } from "./github.service";
  3 | import { MarkdownService } from './markdown.service';
  4 | import { Utilities } from "../helpers";
  5 | import * as marked from 'marked';
  6 | import { IImage } from './';
  7 | 
  8 | declare var toMarkdown: any;
  9 | 
 10 | @Injectable()
 11 | export class WordService {
 12 |     constructor(
 13 |         private _githubService: GithubService,
 14 |         private _markDownService: MarkdownService
 15 |     ) {
 16 | 
 17 |     }
 18 | 
 19 |     insertTemplate(md: string, link: string) {
 20 |         var html = this._markDownService.convertToHtml(md);
 21 |         return this.insertHtml(html, link)
 22 |     }
 23 | 
 24 |     insertHtml(html: string, link: string): Promise {
 25 |         appInsights.trackEvent('insert html', null, { 'length': html.length });
 26 |         var start = performance.now();
 27 |         if (Utilities.isEmpty(html)) return Promise.reject(null);
 28 |         return this._run(context => {
 29 |             return this._insertHtmlIntoWord(context, html, link)
 30 |                 .then(() => this._formatStyles(context))
 31 |                 .then(() => {
 32 |                     var end = performance.now();
 33 |                     appInsights.trackMetric("Insert HTML duration", (end - start) / 1000);
 34 |                 });
 35 |         })
 36 |     }
 37 | 
 38 |     insertNumberedList() {
 39 |         return this._run(context => {
 40 |             var selection = context.document.getSelection();
 41 |             selection.insertHtml('
  1. Item1
  2. Item2
  3. Item3
', Word.InsertLocation.after); 42 | return context.sync(); 43 | }); 44 | } 45 | 46 | insertBulletedList() { 47 | return this._run(context => { 48 | var selection = context.document.getSelection(); 49 | selection.insertHtml('
  • Item1
  • Item2
  • Item3
', Word.InsertLocation.after); 50 | return context.sync(); 51 | }); 52 | } 53 | 54 | getMarkdown(link: string, hasInlinePictures: boolean) { 55 | return this._run(context => { 56 | var html = context.document.body.getHtml(); 57 | return context.sync().then(() => { 58 | var div = document.createElement('tempHtmlDiv'); 59 | div.innerHTML = html.value; 60 | 61 | if (hasInlinePictures) { 62 | var images = div.getElementsByTagName('img'); 63 | var altValue, srcValue; 64 | var toRemove = "Title: "; 65 | for (var i = 0, max = images.length; i < max; i++) { 66 | altValue = images[i].getAttribute('alt'); 67 | altValue = altValue.replace(toRemove, ""); 68 | console.log("altvalue " + altValue); 69 | srcValue = images[i].getAttribute('src'); 70 | if (srcValue.toLowerCase().startsWith("~wrs")) { 71 | images[i].setAttribute('src', link + "/" + altValue); 72 | } 73 | } 74 | } 75 | return this._markDownService.convertToMD(div.innerHTML); 76 | }); 77 | }); 78 | } 79 | 80 | getBase64EncodedStringsOfImages(link: string): OfficeExtension.IPromise { 81 | var start = performance.now(); 82 | var imagesArray: IImage[] = []; 83 | return this._run(context => { 84 | var images = context.document.body.inlinePictures.load(); 85 | return context.sync().then(() => { 86 | for (var i = 0; i < images.items.length; i++) { 87 | var image = { 88 | imageFormat: images.items[i].imageFormat, 89 | altTextTitle: images.items[i].altTextTitle, 90 | altTextDescription: images.items[i].altTextDescription, 91 | height: images.items[i].height, 92 | width: images.items[i].width, 93 | hyperlink: images.items[i].hyperlink, 94 | base64ImageSrc: images.items[i].getBase64ImageSrc() 95 | 96 | } 97 | if (Utilities.isEmpty(image.hyperlink)) { 98 | var uniqueNumber = new Date().getTime(); 99 | var fileName = "Image" + uniqueNumber + "." + image.imageFormat; 100 | image.hyperlink = "images/" + fileName; 101 | image.altTextTitle = "images/" + fileName; 102 | image.altTextDescription = ""; 103 | images.items[i].hyperlink = link + "/" + "images/" + fileName; 104 | images.items[i].altTextTitle = "images/" + fileName; 105 | images.items[i].altTextDescription = ""; 106 | imagesArray.push(image); 107 | } 108 | } 109 | 110 | return context.sync().then(function () { 111 | var end = performance.now(); 112 | appInsights.trackEvent('images loaded from word', null, { "number of images": imagesArray.length }); 113 | appInsights.trackMetric('images load duration', (end - start) / 1000); 114 | return imagesArray; 115 | }); 116 | }); 117 | }); 118 | } 119 | 120 | private _run(batch: (context: Word.RequestContext) => OfficeExtension.IPromise): OfficeExtension.IPromise { 121 | return Word.run(batch).catch(exception => Utilities.error(exception) as OfficeExtension.IPromise); 122 | } 123 | 124 | 125 | private _insertHtmlIntoWord(context: Word.RequestContext, html: string, link: string) { 126 | var body = context.document.body; 127 | var div = document.createElement('tempHtmlDiv'); 128 | div.innerHTML = html; 129 | 130 | var images = div.getElementsByTagName('img'); 131 | var altValue, srcValue, max, i; 132 | for (i = 0, max = images.length; i < max; i++) { 133 | altValue = images[i].parentElement.getAttribute('href'); 134 | srcValue = images[i].getAttribute('src'); 135 | var filename = _.last(srcValue.split('/')); 136 | console.log(images[i].width); 137 | if (!srcValue.toLowerCase().startsWith("http")) { 138 | images[i].setAttribute('src', link + "/" + "images/" + filename); 139 | } 140 | } 141 | 142 | html = div.innerHTML; 143 | body.insertHtml(html, Word.InsertLocation.replace); 144 | return context.sync(); 145 | } 146 | 147 | private _formatStyles(context: Word.RequestContext) { 148 | var tables = context.document.body.tables; 149 | if (tables) tables.load('style'); 150 | 151 | var paragraphs = context.document.body.paragraphs; 152 | if (paragraphs) paragraphs.load(['style', 'text']); 153 | 154 | return context.sync().then(() => { 155 | if (paragraphs) { 156 | _.each(paragraphs.items, paragraph => { 157 | switch (paragraph.style) { 158 | case 'HTML Preformatted': 159 | case 'undefined': 160 | case '': 161 | case 'pl-c': 162 | paragraph.style = 'HTML Preformatted'; 163 | break; 164 | 165 | case 'Normal (Web)': 166 | paragraph.style = 'Normal'; 167 | 168 | default: 169 | paragraph.font.size = 11; 170 | paragraph.font.name = 'Segoe UI'; 171 | paragraph.font.color = '#333333'; 172 | break; 173 | } 174 | }); 175 | } 176 | 177 | if (tables) { 178 | _.each(tables.items, table => { 179 | table.style = "Plain Table 1"; 180 | table.verticalAlignment = Word.VerticalAlignment.center; 181 | table.styleFirstColumn = false; 182 | table.headerRowCount = 0; 183 | table.styleBandedRows = true; 184 | table.autoFitContents(); 185 | }); 186 | } 187 | return context.sync(); 188 | }); 189 | } 190 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/images/black_brandbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Word-Add-in-Markdown-Editor/687e49a0854d0a1a252d5e6a748266773c2e8a4a/MarkdownEditorForWord/src/assets/images/black_brandbar.png -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Word-Add-in-Markdown-Editor/687e49a0854d0a1a252d5e6a748266773c2e8a4a/MarkdownEditorForWord/src/assets/images/logo.png -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/images/logo_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Word-Add-in-Markdown-Editor/687e49a0854d0a1a252d5e6a748266773c2e8a4a/MarkdownEditorForWord/src/assets/images/logo_16.png -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/images/logo_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Word-Add-in-Markdown-Editor/687e49a0854d0a1a252d5e6a748266773c2e8a4a/MarkdownEditorForWord/src/assets/images/logo_32.png -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/images/logo_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Word-Add-in-Markdown-Editor/687e49a0854d0a1a252d5e6a748266773c2e8a4a/MarkdownEditorForWord/src/assets/images/logo_64.png -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/images/logo_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Word-Add-in-Markdown-Editor/687e49a0854d0a1a252d5e6a748266773c2e8a4a/MarkdownEditorForWord/src/assets/images/logo_80.png -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/images/white_brandbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Word-Add-in-Markdown-Editor/687e49a0854d0a1a252d5e6a748266773c2e8a4a/MarkdownEditorForWord/src/assets/images/white_brandbar.png -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/styles/globals.scss: -------------------------------------------------------------------------------- 1 | @import 'theme.scss'; 2 | @import 'mixins.scss'; 3 | 4 | * { 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | *[hidden] { 10 | display: none !important; 11 | } 12 | 13 | html, body { 14 | width: 100%; 15 | height: 100%; 16 | margin: 0; 17 | padding: 0; 18 | overflow-x: hidden; 19 | color: $foreground-primary; 20 | } 21 | 22 | body { 23 | position: relative; 24 | } 25 | 26 | main { 27 | height: 100%; 28 | } 29 | 30 | footer { 31 | width: 100%; 32 | } 33 | 34 | p, h1, h2, h3, h4, h5, h6 { 35 | margin: 0; 36 | padding: 0; 37 | } 38 | 39 | ul { 40 | padding: 0; 41 | } 42 | 43 | .clearfix { 44 | display: block; 45 | clear: both; 46 | height: 0; 47 | } 48 | 49 | .ms-Icon-large { 50 | position: relative; 51 | font-size: 20px; 52 | top: 4px; 53 | } 54 | 55 | .app { 56 | @include flex(); 57 | height: 100vh; 58 | 59 | &__main { 60 | flex: 1 1 0; 61 | @include flex(); 62 | } 63 | 64 | &__footer { 65 | @include background-fill(contain, $background-primary) { 66 | background-image: url("../images/black_brandbar.png"); 67 | } 68 | 69 | cursor: pointer; 70 | height: 80px; 71 | } 72 | } 73 | 74 | repo, file-list, file-tree, file-create, file-detail, login, async-view { 75 | @include flex(); 76 | flex: 1 1 100%; 77 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | @import 'mixins/flex.scss'; 2 | @import 'mixins/background.fill.scss'; 3 | @import 'mixins/ellipsis.scss'; 4 | @import 'mixins/focus.states.scss'; 5 | @import 'mixins/keyframes.scss'; 6 | @import 'mixins/scroll.scss'; -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/styles/mixins/background.fill.scss: -------------------------------------------------------------------------------- 1 | @mixin background-fill($type: contain, $fill-color: transparent) { 2 | background: $fill-color no-repeat center center; 3 | @content; 4 | background-size: $type; 5 | } 6 | 7 | @mixin background-image-circle($size, $url: '') { 8 | @if $url { 9 | background-image: url($url); 10 | } 11 | 12 | width: $size; 13 | height: $size; 14 | border-radius: 100%; 15 | } 16 | 17 | @mixin background-image-square($size, $url: '') { 18 | @if $url { 19 | background-image: url($url); 20 | } 21 | 22 | width: $size; 23 | height: $size; 24 | } 25 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/styles/mixins/ellipsis.scss: -------------------------------------------------------------------------------- 1 | @mixin ellipsis($width: auto) { 2 | width: $width; 3 | max-width: $width; 4 | overflow: hidden; 5 | white-space: nowrap; 6 | text-overflow: ellipsis; 7 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/styles/mixins/flex.scss: -------------------------------------------------------------------------------- 1 | @mixin flex($direction: column, $wrap: nowrap, $horizontal-alignment: null, $vertical-alignment: null) { 2 | display: flex; 3 | flex-direction: $direction; 4 | flex-wrap: $wrap; 5 | 6 | @if $horizontal-alignment { 7 | justify-content: $horizontal-alignment; 8 | } 9 | 10 | @if $vertical-alignment { 11 | align-items: $vertical-alignment; 12 | } 13 | } 14 | 15 | @mixin inline-flex($horizontal-alignment: null, $vertical-alignment: null) { 16 | display: inline-flex; 17 | 18 | @if $horizontal-alignment { 19 | justify-content: $horizontal-alignment; 20 | } 21 | 22 | @if $vertical-alignment { 23 | align-items: $vertical-alignment; 24 | } 25 | } 26 | 27 | @mixin center-flex() { 28 | @include flex(column, nowrap, center, center); 29 | } 30 | 31 | @mixin stretch-flex() { 32 | @include flex(column, nowrap, stretch, stretch); 33 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/styles/mixins/focus.states.scss: -------------------------------------------------------------------------------- 1 | @mixin focus-states($background: null, $foreground: null, $scale: true) { 2 | transition: background ease-in-out 0.1s, color ease-in-out 0.1s; 3 | 4 | &:active, &:hover { 5 | @if $background != null { 6 | background: $background; 7 | } 8 | @else { 9 | background: rgba(0,0,0,0.1); 10 | } 11 | 12 | @if $foreground != null { 13 | color: $foreground; 14 | } 15 | 16 | cursor: pointer; 17 | } 18 | 19 | &:active { 20 | @if $background != null { 21 | background: $background; 22 | } 23 | @else { 24 | background: rgba(0,0,0,0.2); 25 | } 26 | 27 | @if($scale) { 28 | transform: scale3d(0.98, 0.98, 1); 29 | } 30 | } 31 | 32 | &--disabled { 33 | opacity: 0.6; 34 | pointer-events: none; 35 | cursor: not-allowed; 36 | 37 | &:active, &:hover { 38 | background: transparent; 39 | } 40 | 41 | @if($scale) { 42 | &:active { 43 | transform: none; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/styles/mixins/keyframes.scss: -------------------------------------------------------------------------------- 1 | @mixin keyframes($animation-name) { 2 | @-webkit-keyframes #{$animation-name} { 3 | @content; 4 | } 5 | @-moz-keyframes #{$animation-name} { 6 | @content; 7 | } 8 | @-ms-keyframes #{$animation-name} { 9 | @content; 10 | } 11 | @-o-keyframes #{$animation-name} { 12 | @content; 13 | } 14 | @keyframes #{$animation-name} { 15 | @content; 16 | } 17 | } 18 | 19 | @mixin animation($str) { 20 | -webkit-animation: #{$str}; 21 | -moz-animation: #{$str}; 22 | -o-animation: #{$str}; 23 | animation: #{$str}; 24 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/styles/mixins/scroll.scss: -------------------------------------------------------------------------------- 1 | @mixin scroll() { 2 | overflow: auto; 3 | height: 100%; 4 | } 5 | 6 | @mixin vScroll() { 7 | @include scroll(); 8 | overflow-x: hidden; 9 | } 10 | 11 | @mixin hScroll() { 12 | @include scroll(); 13 | overflow-y: hidden; 14 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/styles/spinner.scss: -------------------------------------------------------------------------------- 1 | @import 'theme.scss'; 2 | @import 'mixins.scss'; 3 | 4 | .ms-progress-component { 5 | height: 100%; 6 | background-color: $background-primary; 7 | @include flex(); 8 | 9 | &__main { 10 | height: 100%; 11 | flex: 1 1 0px; 12 | padding: $default-spacing; 13 | @include center-flex(); 14 | } 15 | 16 | &__title { 17 | padding: 0 $default-spacing; 18 | } 19 | 20 | &__logo { 21 | height: 80px; 22 | margin-bottom: $default-spacing; 23 | } 24 | 25 | &__footer { 26 | @include inline-flex(center, center); 27 | margin-bottom: $default-spacing * 5; 28 | } 29 | } 30 | 31 | .ms-progress { 32 | @include center-flex(); 33 | 34 | .ms-ProgressIndicator-itemDescription, 35 | .ms-ProgressIndicator-itemName { 36 | color: $foreground-white; 37 | text-align: center; 38 | @include ellipsis(); 39 | width: 100vw; 40 | } 41 | 42 | .ms-Spinner { 43 | font-size: 7px; 44 | width: 1em; 45 | height: 1em; 46 | border-radius: 100%; 47 | @include animation(spinner 0.7s infinite ease-in-out); 48 | transform: translateZ(0); 49 | margin-bottom: $default-spacing * 2; 50 | } 51 | } 52 | 53 | @include keyframes(spinner) { 54 | 0%, 100% { 55 | box-shadow: 0em -2.6em 0em 0em rgba($foreground-white, 1), 1.8em -1.8em 0 0em rgba($foreground-white, 0.2), 2.5em 0em 0 0em rgba($foreground-white, 0.2), 1.75em 1.75em 0 0em rgba($foreground-white, 0.2), 0em 2.5em 0 0em rgba($foreground-white, 0.2), -1.8em 1.8em 0 0em rgba($foreground-white, 0.2), -2.6em 0em 0 0em rgba($foreground-white, 0.5), -1.8em -1.8em 0 0em rgba($foreground-white, 0.7); 56 | } 57 | 58 | 12.5% { 59 | box-shadow: 0em -2.6em 0em 0em rgba($foreground-white, 0.7), 1.8em -1.8em 0 0em rgba($foreground-white, 1), 2.5em 0em 0 0em rgba($foreground-white, 0.2), 1.75em 1.75em 0 0em rgba($foreground-white, 0.2), 0em 2.5em 0 0em rgba($foreground-white, 0.2), -1.8em 1.8em 0 0em rgba($foreground-white, 0.2), -2.6em 0em 0 0em rgba($foreground-white, 0.2), -1.8em -1.8em 0 0em rgba($foreground-white, 0.5); 60 | } 61 | 62 | 25% { 63 | box-shadow: 0em -2.6em 0em 0em rgba($foreground-white, 0.5), 1.8em -1.8em 0 0em rgba($foreground-white, 0.7), 2.5em 0em 0 0em rgba($foreground-white, 1), 1.75em 1.75em 0 0em rgba($foreground-white, 0.2), 0em 2.5em 0 0em rgba($foreground-white, 0.2), -1.8em 1.8em 0 0em rgba($foreground-white, 0.2), -2.6em 0em 0 0em rgba($foreground-white, 0.2), -1.8em -1.8em 0 0em rgba($foreground-white, 0.2); 64 | } 65 | 66 | 37.5% { 67 | box-shadow: 0em -2.6em 0em 0em rgba($foreground-white, 0.2), 1.8em -1.8em 0 0em rgba($foreground-white, 0.5), 2.5em 0em 0 0em rgba($foreground-white, 0.7), 1.75em 1.75em 0 0em rgba($foreground-white, 1), 0em 2.5em 0 0em rgba($foreground-white, 0.2), -1.8em 1.8em 0 0em rgba($foreground-white, 0.2), -2.6em 0em 0 0em rgba($foreground-white, 0.2), -1.8em -1.8em 0 0em rgba($foreground-white, 0.2); 68 | } 69 | 70 | 50% { 71 | box-shadow: 0em -2.6em 0em 0em rgba($foreground-white, 0.2), 1.8em -1.8em 0 0em rgba($foreground-white, 0.2), 2.5em 0em 0 0em rgba($foreground-white, 0.5), 1.75em 1.75em 0 0em rgba($foreground-white, 0.7), 0em 2.5em 0 0em rgba($foreground-white, 1), -1.8em 1.8em 0 0em rgba($foreground-white, 0.2), -2.6em 0em 0 0em rgba($foreground-white, 0.2), -1.8em -1.8em 0 0em rgba($foreground-white, 0.2); 72 | } 73 | 74 | 62.5% { 75 | box-shadow: 0em -2.6em 0em 0em rgba($foreground-white, 0.2), 1.8em -1.8em 0 0em rgba($foreground-white, 0.2), 2.5em 0em 0 0em rgba($foreground-white, 0.2), 1.75em 1.75em 0 0em rgba($foreground-white, 0.5), 0em 2.5em 0 0em rgba($foreground-white, 0.7), -1.8em 1.8em 0 0em rgba($foreground-white, 1), -2.6em 0em 0 0em rgba($foreground-white, 0.2), -1.8em -1.8em 0 0em rgba($foreground-white, 0.2); 76 | } 77 | 78 | 75% { 79 | box-shadow: 0em -2.6em 0em 0em rgba($foreground-white, 0.2), 1.8em -1.8em 0 0em rgba($foreground-white, 0.2), 2.5em 0em 0 0em rgba($foreground-white, 0.2), 1.75em 1.75em 0 0em rgba($foreground-white, 0.2), 0em 2.5em 0 0em rgba($foreground-white, 0.5), -1.8em 1.8em 0 0em rgba($foreground-white, 0.7), -2.6em 0em 0 0em rgba($foreground-white, 1), -1.8em -1.8em 0 0em rgba($foreground-white, 0.2); 80 | } 81 | 82 | 87.5% { 83 | box-shadow: 0em -2.6em 0em 0em rgba($foreground-white, 0.2), 1.8em -1.8em 0 0em rgba($foreground-white, 0.2), 2.5em 0em 0 0em rgba($foreground-white, 0.2), 1.75em 1.75em 0 0em rgba($foreground-white, 0.2), 0em 2.5em 0 0em rgba($foreground-white, 0.2), -1.8em 1.8em 0 0em rgba($foreground-white, 0.5), -2.6em 0em 0 0em rgba($foreground-white, 0.7), -1.8em -1.8em 0 0em rgba($foreground-white, 1); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/styles/theme.scss: -------------------------------------------------------------------------------- 1 | /* Default Variables */ 2 | $default-spacing: 15px; 3 | 4 | /* Background Colors */ 5 | $background-primary: #212121; 6 | $background-light: #DEDEDE; 7 | $background-lighter: #E6E6E6; 8 | $background-lightest: #F4F4F4; 9 | $background-white: #FFFFFF; 10 | 11 | /* Foreground Colors */ 12 | $foreground-primary: #333333; 13 | $foreground-white: #FFFFFF; 14 | $foreground-light: #666666; 15 | $foreground-lighter: #999999; 16 | $foreground-lightest: #BABABA; 17 | 18 | /* Accent Colors */ 19 | $accent-background-primary: #0078d7; 20 | $accent-foreground-primary: #deecf9; -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/templates/contributing.md: -------------------------------------------------------------------------------- 1 | # Contribute to this code sample 2 | 3 | Thank you for your interest in our sample 4 | 5 | * [Ways to contribute](#ways-to-contribute) 6 | * [To contribute using Git](#to-contribute-using-git) 7 | * [Contribute code](#contribute-code) 8 | * [FAQ](#faq) 9 | * [More resources](#more-resources) 10 | 11 | ## Ways to contribute 12 | 13 | Here are some ways you can contribute to this sample: 14 | 15 | * Add better comments to the sample code. 16 | * Fix issues opened in GitHub against this sample. 17 | * Add a new feature to the sample. 18 | 19 | We want your contributions. Help the developer community by improving this sample. 20 | Contributions can include Bug fixes, new features, and better code documentation. 21 | Submit code comment contributions where you want a better explanation of what's going on. 22 | See a good example of [code commenting](https://github.com/OfficeDev/O365-Android-Microsoft-Graph-Connect/blob/master/app/src/main/java/com/microsoft/office365/connectmicrosoftgraph/AuthenticationManager.java). 23 | 24 | Another great way to improve the sample in this repository is to take on some of the open issues filed against the repository. You may have a solution to an bug in the sample code that hasn't been addressed. Fix the issue and then create a pull request following our [Contribute code](#contribute-code) guidance. 25 | 26 | If you want to add a new feature to the sample, be sure you have the agreement of the repository owner before writing the code. Start by opening an issue in the repository. Use the new issue to propose the feature. The repository owner will respond and will usually ask you for more information. When the owner agrees to take the new feature, code it and submit a pull request. 27 | 28 | ## To contribute using Git 29 | For most contributions, you'll be asked to sign a Contribution License Agreement (CLA). For those contributions that need it, The Office 365 organization on GitHub will send a link to the CLA that we want you to sign via email. 30 | By signing the CLA, you acknowledge the rights of the GitHub community to use any code that you submit. The intellectual property represented by the code contribution is licensed for use by Microsoft open source projects. 31 | 32 | If Office 365 emails a CLA to you, you need to sign it before you can contribute large submissions to a project. You only need to complete and submit it once. 33 | Read the CLA carefully. You may need to have your employer sign it. 34 | 35 | Signing the CLA does not grant you rights to commit to the main repository, but it does mean that the Office Developer and Office Developer Content Publishing teams will be able to review and approve your contributions. You will be credited for your submissions. 36 | 37 | Pull requests are typically reviewed within 10 business days. 38 | 39 | ## Use GitHub, Git, and this repository 40 | 41 | **Note:** Most of the information in this section can be found in [GitHub Help] articles. If you're familiar with Git and GitHub, skip to the **Contribute code** section for the specifics of the code contributions for this repository. 42 | 43 | ### To set up your fork of the repository 44 | 45 | 1. Set up a GitHub account so you can contribute to this project. If you haven't done this, go to [GitHub](https://github.com/join) and do it now. 46 | 2. Install Git on your computer. Follow the steps in the [Setting up Git Tutorial] [Set Up Git]. 47 | 3. Create your own fork of this repository. To do this, at the top of the page, choose the **Fork** button. 48 | 4. Copy your fork to your computer. To do this, open Git Bash. At the command prompt enter: 49 | 50 | git clone https://github.com//.git 51 | 52 | Next, create a reference to the root repository by entering these commands: 53 | 54 | cd 55 | git remote add upstream https://github.com/OfficeDev/.git 56 | git fetch upstream 57 | 58 | Congratulations! You've now set up your repository. You won't need to repeat these steps again. 59 | 60 | ## Contribute code 61 | 62 | To make the contribution process as seamless as possible, follow these steps. 63 | 64 | ### To contribute code 65 | 66 | 1. Create a new branch. 67 | 2. Add new code or modify existing code. 68 | 3. Submit a pull request to the main repository. 69 | 4. Await notification of acceptance and merge. 70 | 5. Delete the branch. 71 | 72 | 73 | ### To create a new branch 74 | 75 | 1. Open Git Bash. 76 | 2. At the Git Bash command prompt, type `git pull upstream master:`. This creates a new branch locally that is copied from the latest OfficeDev master branch. 77 | 3. At the Git Bash command prompt, type `git push origin `. This alerts GitHub to the new branch. You should now see the new branch in your fork of the repository on GitHub. 78 | 4. At the Git Bash command prompt, type `git checkout ` to switch to your new branch. 79 | 80 | ### Add new code or modify existing code 81 | 82 | Navigate to the repository on your computer. On a Windows PC, the repository files are in `C:\Users\\`. 83 | 84 | Use the IDE of your choice to modify and build the sample. Once you have completed your change, commented your code, and test, check the code 85 | into the remote branch on GitHub. 86 | 87 | #### Code contribution checklist 88 | Be sure to satisfy all of the requirements in the following list before submitting a pull request: 89 | 90 | 91 | - Follow the code style found in the cloned repository code. Our Android code follows the style conventions found in the [Code Style for Contributors](https://source.android.com/source/code-style.html) guide. 92 | - Code must be tested. 93 | - Test the sample UI thoroughly to be sure nothing has been broken by your change. 94 | - Keep the size of your code change reasonable. If the repository owner cannot review your code change in 4 hours or less, your pull request may not be reviewed and approved quickly. 95 | - Avoid unnecessary changes to cloned or forked code. The reviewer will use a tool to find the differences between your code and the original code. Whitespace changes are called out along with your code. Be sure your changes will help improve the content. 96 | 97 | ### Push your code to the remote GitHub branch 98 | The files in `C:\Users\\` are a working copy of the new branch that you created in your local repository. Changing anything in this folder doesn't affect the local repository until you commit a change. To commit a change to the local repository, type the following commands in GitBash: 99 | 100 | git add . 101 | git commit -v -a -m "" 102 | 103 | The `add` command adds your changes to a staging area in preparation for committing them to the repository. The period after the `add` command specifies that you want to stage all of the files that you added or modified, checking subfolders recursively. (If you don't want to commit all of the changes, you can add specific files. You can also undo a commit. For help, type `git add -help` or `git status`.) 104 | 105 | The `commit` command applies the staged changes to the repository. The switch `-m` means you are providing the commit comment in the command line. The -v and -a switches can be omitted. The -v switch is for verbose output from the command, and -a does what you already did with the add command. 106 | 107 | You can commit multiple times while you are doing your work, or you can commit once when you're done. 108 | 109 | ### Submit a pull request to the master repository 110 | 111 | When you're finished with your work and are ready to have it merged into the master repository, follow these steps. 112 | 113 | #### To submit a pull request to the master repository 114 | 115 | 1. In the Git Bash command prompt, type `git push origin `. In your local repository, `origin` refers to your GitHub repository that you cloned the local repository from. This command pushes the current state of your new branch, including all commits made in the previous steps, to your GitHub fork. 116 | 2. On the GitHub site, navigate in your fork to the new branch. 117 | 3. Choose the **Pull Request** button at the top of the page. 118 | 4. Verify the Base branch is `OfficeDev/@master` and the Head branch is `/@`. 119 | 5. Choose the **Update Commit Range** button. 120 | 6. Add a title to your pull request, and describe all the changes you're making. 121 | 7. Submit the pull request. 122 | 123 | One of the site administrators will process your pull request. Your pull request will surface on the `OfficeDev/` site under Issues. When the pull request is accepted, the issue will be resolved. 124 | 125 | ### Repository owner code review 126 | The owner of the repository will review your pull request to be sure that all requirements are met. If the reviewer 127 | finds any issues, she will communicate with you and ask you to address them and then submit a new pull request. If your pull 128 | request is accepted, then the repository owner will tell you that your pull request is to be merged. 129 | 130 | ### Create a new branch after merge 131 | 132 | After a branch is successfully merged (that is, your pull request is accepted), don't continue working in that local branch. This can lead to merge conflicts if you submit another pull request. To do another update, create a new local branch from the successfully merged upstream branch, and then delete your initial local branch. 133 | 134 | For example, if your local branch X was successfully merged into the OfficeDev/O365-Android-Microsoft-Graph-Connect master branch and you want to make additional updates to the code that was merged. Create a new local branch, X2, from the OfficeDev/O365-Android-Microsoft-Graph-Connect branch. To do this, open GitBash and execute the following commands: 135 | 136 | cd 137 | git pull upstream master:X2 138 | git push origin X2 139 | 140 | You now have local copies (in a new local branch) of the work that you submitted in branch X. The X2 branch also contains all the work other developers have merged, so if your work depends on others' work (for example, a base class), it is available in the new branch. You can verify that your previous work (and others' work) is in the branch by checking out the new branch... 141 | 142 | git checkout X2 143 | 144 | ...and verifying the code. (The `checkout` command updates the files in `C:\Users\\O365-Android-Microsoft-Graph-Connect` to the current state of the X2 branch.) Once you check out the new branch, you can make updates to the code and commit them as usual. However, to avoid working in the merged branch (X) by mistake, it's best to delete it (see the following **Delete a branch** section). 145 | 146 | ### Delete a branch 147 | 148 | Once your changes are successfully merged into the main repository, delete the branch you used because you no longer need it. Any additional work should be done in a new branch. 149 | 150 | #### To delete a branch 151 | 152 | 1. In the Git Bash command prompt, type `git checkout master`. This ensures that you aren't in the branch to be deleted (which isn't allowed). 153 | 2. Next, at the command prompt, type `git branch -d `. This deletes the branch on your computer only if it has been successfully merged to the upstream repository. (You can override this behavior with the `-D` flag, but first be sure you want to do this.) 154 | 3. Finally, type `git push origin :` at the command prompt (a space before the colon and no space after it). This will delete the branch on your github fork. 155 | 156 | Congratulations, you have successfully contributed to the sample app! 157 | 158 | 159 | ## FAQ 160 | 161 | ### How do I get a GitHub account? 162 | 163 | Fill out the form at [Join GitHub](https://github.com/join) to open a free GitHub account. 164 | 165 | ### Where do I get a Contributor's License Agreement? 166 | 167 | You will automatically be sent a notice that you need to sign the Contributor's License Agreement (CLA) if your pull request requires one. 168 | 169 | As a community member, **you must sign the CLA before you can contribute large submissions to this project**. You only need complete and submit the CLA document once. Carefully review the document. You may be required to have your employer sign the document. 170 | 171 | ### What happens with my contributions? 172 | 173 | When you submit your changes, via a pull request, our team will be notified and will review your pull request. You will receive notifications about your pull request from GitHub; you may also be notified by someone from our team if we need more information. If your pull request is approved, we'll update the documentation on GitHub and on MSDN. We reserve the right to edit your submission for legal, style, clarity, or other issues. 174 | 175 | ### Who approves pull requests? 176 | 177 | The owner of the sample repository approves pull requests. 178 | 179 | ### How soon will I get a response about my change request? 180 | 181 | Pull requests are typically reviewed within 10 business days. 182 | 183 | 184 | ## More resources 185 | 186 | * To learn more about Markdown, go to the Git creator's site [Daring Fireball]. 187 | * To learn more about using Git and GitHub, check out the [GitHub Help section] [GitHub Help]. 188 | 189 | [GitHub Home]: http://github.com 190 | [GitHub Help]: http://help.github.com/ 191 | [Set Up Git]: http://help.github.com/win-set-up-git/ 192 | [Markdown Home]: http://daringfireball.net/projects/markdown/ 193 | [Daring Fireball]: http://daringfireball.net/ -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/templates/mit-license.md: -------------------------------------------------------------------------------- 1 | Office Add-in UX Design Patterns Code 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | The MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/templates/readme.md: -------------------------------------------------------------------------------- 1 | # Project Title 2 | 3 | One Paragraph of project description goes here 4 | 5 | ## Getting Started 6 | 7 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. 8 | 9 | ### Prerequisites 10 | 11 | You'll need: 12 | 13 | * [Visual Studio 2015](https://www.visualstudio.com/downloads/download-visual-studio-vs.aspx) 14 | * [Office Developer Tools for Visual Studio](https://www.visualstudio.com/en-us/features/office-tools-vs.aspx) 15 | * Excel 2016, version 6769.2011 or later 16 | * Node.js 17 | 18 | 19 | ### Installing 20 | 21 | A step by step series of examples that tell you have to get a development env running 22 | 23 | Stay what the step will be 24 | 25 | ``` 26 | Give the example 27 | ``` 28 | 29 | And repeat 30 | 31 | ``` 32 | until finished 33 | ``` 34 | 35 | End with an example of getting some data out of the system or using it for a little demo 36 | 37 | ## Running the tests 38 | 39 | Explain how to run the automated tests for this system 40 | 41 | ### Break down into end to end tests 42 | 43 | Explain what these tests test and why 44 | 45 | ``` 46 | Give an example 47 | ``` 48 | 49 | ### And coding style tests 50 | 51 | Explain what these tests test and why 52 | 53 | ``` 54 | Give an example 55 | ``` 56 | 57 | ## Deployment 58 | 59 | Add additional notes about how to deploy this on a live system 60 | 61 | ## Built With 62 | 63 | * Dropwizard - Bla bla bla 64 | * Maven - Maybe 65 | * Atom - ergaerga 66 | 67 | ## Contributing 68 | 69 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 70 | 71 | ## Versioning 72 | 73 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). 74 | 75 | ## Authors 76 | 77 | * **Billie Thompson** - *Initial work* - [PurpleBooth](https://github.com/PurpleBooth) 78 | 79 | See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. 80 | 81 | ## License 82 | 83 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 84 | 85 | ## Acknowledgments 86 | 87 | * Hat tip to anyone who's code was used 88 | * Inspiration 89 | * etc 90 | 91 | ## Additional resources 92 | 93 | * [Office Dev Center](http://dev.office.com/) 94 | 95 | ## Copyright 96 | Copyright (c) 2016 Microsoft. All rights reserved. 97 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/assets/templates/simple-file.md: -------------------------------------------------------------------------------- 1 | # Excel-Add-in-JS-WoodGrove-Expense-Trends 2 | 3 | The WoodGrove Bank Expense Trends add-in demonstrates how you can use the new JavaScript API for Microsoft Excel 2016 to create a compelling Excel add-in. With Expense Trends, you can import expense transactions into the workbook, create dashboard and trackers, view and analyze trends, and track special transactions such as charitable donations and follow up items. The sample provides two experiences: one with task pane and another with add-in commands. The following figures show the main screens of this add-in. 4 | 5 | _Applies to: Excel 2016, Excel for iPad, Excel for Mac_ 6 | 7 | ## Table of Contents 8 | 9 | * [Prerequisites](#prerequisites) 10 | * [Run the project](#run-the-project) 11 | * [Additional resources](#additional-resources) 12 | 13 | ## Prerequisites 14 | 15 | You'll need: 16 | 17 | * [Visual Studio 2015](https://www.visualstudio.com/downloads/download-visual-studio-vs.aspx) 18 | * [Office Developer Tools for Visual Studio](https://www.visualstudio.com/en-us/features/office-tools-vs.aspx) 19 | * Excel 2016, version 6769.2011 or later 20 | 21 | ## Properties 22 | | Property | Type |Description 23 | |:---------------|:--------|:----------| 24 | |style|string|Gets or sets the style used for the body. This is the name of the pre-installed or custom style.| 25 | |text|string|Gets the text of the body. Use the insertText method to insert text. Read-only.| 26 | 27 | >See property access [examples.](#property-access-examples) 28 | 29 | ## Relationships 30 | | Relationship | Type |Description| 31 | |:---------------|:--------|:----------| 32 | |contentControls|[ContentControlCollection](contentcontrolcollection.md)|Gets the collection of rich text content control objects that are in the body. Read-only.| 33 | |font|[Font](font.md)|Gets the text format of the body. Use this to get and set font name, size, color, and other properties. Read-only.| 34 | |inlinePictures|[InlinePictureCollection](inlinepicturecollection.md)|Gets the collection of inlinePicture objects that are in the body. The collection does not include floating images. Read-only.| 35 | |paragraphs|[ParagraphCollection](paragraphcollection.md)|Gets the collection of paragraph objects that are in the body. Read-only.| 36 | |parentContentControl|[ContentControl](contentcontrol.md)|Gets the content control that contains the body. Returns null if there isn't a parent content control. Read-only.| 37 | 38 | ## Method details 39 | 40 | ### clear() 41 | Clears the contents of the body object. The user can perform the undo operation on the cleared content. 42 | 43 | #### Syntax 44 | ```js 45 | bodyObject.clear(); 46 | ``` 47 | 48 | #### Parameters 49 | None 50 | 51 | #### Returns 52 | void 53 | 54 | #### Examples 55 | ```js 56 | // Run a batch operation against the Word object model. 57 | Word.run(function (context) { 58 | 59 | // Create a proxy object for the document body. 60 | var body = context.document.body; 61 | 62 | // Queue a commmand to clear the contents of the body. 63 | body.clear(); 64 | 65 | // Synchronize the document state by executing the queued commands, 66 | // and return a promise to indicate task completion. 67 | return context.sync().then(function () { 68 | console.log('Cleared the body contents.'); 69 | }); 70 | }) 71 | .catch(function (error) { 72 | console.log("Error: " + JSON.stringify(error)); 73 | if (error instanceof OfficeExtension.Error) { 74 | console.log("Debug info: " + JSON.stringify(error.debugInfo)); 75 | } 76 | }); 77 | ``` 78 | 79 | The [Silly stories](https://aka.ms/sillystorywordaddin) add-in sample shows how the **clear** method can be used to clear the contents of a document. 80 | 81 | ## Support details 82 | 83 | Use the [requirement set](https://msdn.microsoft.com/EN-US/library/office/mt590206.aspx) in run time checks to make sure your application is supported by the host version of Word. For more information about Office host application and server requirements, see [Requirements for running Office Add-ins](https://msdn.microsoft.com/EN-US/library/office/dn833104.aspx). 84 | 85 | 86 | [body.insertOoxml]: https://github.com/OfficeDev/Word-Add-in-DocumentAssembly/blob/master/WordAPIDocAssemblySampleWeb/App/Home/Home.js#L127 "insert OOXML" 87 | [body.insertParagraph]: https://github.com/OfficeDev/Word-Add-in-DocumentAssembly/blob/master/WordAPIDocAssemblySampleWeb/App/Home/Home.js#L153 "insert paragraph" 88 | [body.search]: https://github.com/OfficeDev/Word-Add-in-DocumentAssembly/blob/master/WordAPIDocAssemblySampleWeb/App/Home/Home.js#L261 "body search" -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Markdown Editor for Word 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 |

MARKDOWN EDITOR

31 |

for Word

32 |
33 |
34 |
35 |
36 |
Loading
37 |
Fetching libraries
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/es6'; 2 | import 'core-js/es7/reflect'; 3 | require('zone.js/dist/zone'); 4 | 5 | if (process.env.ENV === 'production') { 6 | // Production 7 | } else { 8 | // Development 9 | Error['stackTraceLimit'] = Infinity; 10 | require('zone.js/dist/long-stack-trace-zone'); 11 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/src/vendor.ts: -------------------------------------------------------------------------------- 1 | // Angular 2 2 | import '@angular/platform-browser'; 3 | import '@angular/platform-browser-dynamic'; 4 | import '@angular/core'; 5 | import '@angular/common'; 6 | import '@angular/forms'; 7 | import '@angular/http'; 8 | import '@angular/router'; 9 | 10 | // RxJS 11 | import 'rxjs'; 12 | 13 | // External scripts 14 | import './assets/scripts/stringview.js'; -------------------------------------------------------------------------------- /MarkdownEditorForWord/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "removeComments": false, 10 | "noImplicitAny": false, 11 | "noEmitOnError": true, 12 | "rootDir": "src", 13 | "outDir": "dist" 14 | }, 15 | "exclude": [ 16 | "node_modules" 17 | ] 18 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "curly": false, 8 | "eofline": true, 9 | "indent": [ 10 | "spaces" 11 | ], 12 | "max-line-length": [ 13 | true, 14 | 140 15 | ], 16 | "member-ordering": [ 17 | true, 18 | "public-before-private", 19 | "static-before-instance", 20 | "variables-before-functions" 21 | ], 22 | "no-arg": true, 23 | "no-construct": true, 24 | "no-duplicate-key": true, 25 | "no-duplicate-variable": true, 26 | "no-empty": true, 27 | "no-eval": true, 28 | "no-trailing-whitespace": true, 29 | "no-unused-expression": true, 30 | "no-unused-variable": true, 31 | "no-unreachable": true, 32 | "no-use-before-declare": true, 33 | "one-line": [ 34 | true, 35 | "check-open-brace", 36 | "check-catch", 37 | "check-else", 38 | "check-whitespace" 39 | ], 40 | "quotemark": [ 41 | true, 42 | "single" 43 | ], 44 | "semicolon": true, 45 | "trailing-comma": true, 46 | "triple-equals": true, 47 | "variable-name": false, 48 | "directive-selector-name": [ 49 | true, 50 | "camelCase" 51 | ], 52 | "component-selector-name": [ 53 | true, 54 | "kebab-case" 55 | ], 56 | "directive-selector-type": [ 57 | true, 58 | "attribute" 59 | ], 60 | "component-selector-type": [ 61 | true, 62 | "element" 63 | ], 64 | "use-input-property-decorator": true, 65 | "use-output-property-decorator": true, 66 | "use-host-property-decorator": true, 67 | "no-input-rename": true, 68 | "no-output-rename": true, 69 | "use-life-cycle-interface": true, 70 | "use-pipe-transform-interface": true, 71 | "component-class-suffix": true, 72 | "directive-class-suffix": true 73 | } 74 | } -------------------------------------------------------------------------------- /MarkdownEditorForWord/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-editor-for-word", 3 | "dependencies": {}, 4 | "globalDependencies": { 5 | "applicationinsights-js": "registry:dt/applicationinsights-js#0.23.2+20160730040946", 6 | "core-js": "registry:dt/core-js#0.0.0+20160914114559", 7 | "jquery": "registry:dt/jquery#1.10.0+20160929162922", 8 | "marked": "registry:dt/marked#0.0.0+20160325085301", 9 | "node": "registry:dt/node#6.0.0+20161019125345", 10 | "office-js": "registry:dt/office-js#0.0.0+20161003175149", 11 | "underscore": "registry:dt/underscore#1.8.3+20160908111004" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MarkdownEditorForWord/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./config/webpack.dev.js'); -------------------------------------------------------------------------------- /MarkdownEditorForWordAddin/MarkdownEditorForWordAddin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F180F779-7692-4635-9C85-84D2C5A8F9CA} 8 | Library 9 | Properties 10 | Word_Addin_Ang2_Word_to_MD 11 | Word-Addin-Ang2-Word-to-MD 12 | v4.5 13 | 15.0 14 | 512 15 | {C1CDDADD-2546-481F-9697-4EA41081F2FC};{14822709-B5A1-4724-98CA-57A101D1B079};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | False 17 | {61f6acab-d419-425b-a7c4-a68de7733326} 18 | {2a0f2e9b-b8e4-4bec-934a-758e2655933e} 19 | {$guid12$} 20 | {1d306f29-5a2e-49d8-be82-8b72641bc572} 21 | {7122144d-a594-4c14-9c20-57de61748382} 22 | OfficeApp 23 | 12.0 24 | 12.2 25 | 26 | 27 | true 28 | full 29 | false 30 | bin\Debug\ 31 | DEBUG;TRACE 32 | prompt 33 | 4 34 | false 35 | 36 | 37 | pdbonly 38 | true 39 | bin\Release\ 40 | TRACE 41 | prompt 42 | 4 43 | false 44 | 45 | 46 | 47 | {648ba33d-3a1b-4179-bd9c-37ad80e9b6ed} 48 | 49 | 50 | manifest-oemanifest 51 | 52 | 53 | 54 | 10.0 55 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 56 | 57 | 58 | -------------------------------------------------------------------------------- /MarkdownEditorForWordAddin/MarkdownEditorForWordAddin/MarkdownEditorForWordAddinManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 8 | 9 | af8fa5ba-4010-4bcc-9e03-a91ddadf6dd3 10 | 1.0.0.0 11 | Microsoft 12 | en-US 13 | 14 | 15 | 16 | 17 | 18 | https://github.com 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ReadWriteDocument 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | <Description resid="ME4W.GetStarted.Description"/> 35 | <LearnMoreUrl resid="ME4W.GetStarted.LearnMoreUrl"/> 36 | </GetStarted> 37 | <ExtensionPoint xsi:type="PrimaryCommandSurface"> 38 | <OfficeTab id="TabHome"> 39 | <Group id="ME4W.Group1"> 40 | <Label resid="ME4W.Group1Label" /> 41 | <Icon> 42 | <bt:Image size="16" resid="ME4W.tpicon_16x16" /> 43 | <bt:Image size="32" resid="ME4W.tpicon_32x32" /> 44 | <bt:Image size="80" resid="ME4W.tpicon_80x80" /> 45 | </Icon> 46 | <Control xsi:type="Button" id="ME4W.TaskpaneButton"> 47 | <Label resid="ME4W.TaskpaneButton.Label" /> 48 | <Supertip> 49 | <Title resid="ME4W.TaskpaneButton.Label" /> 50 | <Description resid="ME4W.TaskpaneButton.Tooltip" /> 51 | </Supertip> 52 | <Icon> 53 | <bt:Image size="16" resid="ME4W.tpicon_16x16" /> 54 | <bt:Image size="32" resid="ME4W.tpicon_32x32" /> 55 | <bt:Image size="80" resid="ME4W.tpicon_80x80" /> 56 | </Icon> 57 | <Action xsi:type="ShowTaskpane"> 58 | <TaskpaneId>ButtonId1</TaskpaneId> 59 | <SourceLocation resid="ME4W.Taskpane.Url" /> 60 | </Action> 61 | </Control> 62 | </Group> 63 | </OfficeTab> 64 | </ExtensionPoint> 65 | </DesktopFormFactor> 66 | </Host> 67 | </Hosts> 68 | 69 | <Resources> 70 | <bt:Images> 71 | <bt:Image id="ME4W.tpicon_16x16" DefaultValue="https://localhost:3000/assets/images/logo_16.png" /> 72 | <bt:Image id="ME4W.tpicon_32x32" DefaultValue="https://localhost:3000/assets/images/logo_32.png" /> 73 | <bt:Image id="ME4W.tpicon_80x80" DefaultValue="https://localhost:3000/assets/images/logo_80.png" /> 74 | </bt:Images> 75 | <bt:Urls> 76 | <bt:Url id="ME4W.Taskpane.Url" DefaultValue="https://localhost:3000/index.html" /> 77 | <bt:Url id="ME4W.GetStarted.LearnMoreUrl" DefaultValue="https://go.microsoft.com/fwlink/?LinkId=276812" /> 78 | </bt:Urls> 79 | <bt:ShortStrings> 80 | <bt:String id="ME4W.TaskpaneButton.Label" DefaultValue="Edit markdown" /> 81 | <bt:String id="ME4W.Group1Label" DefaultValue="Markdown Writer" /> 82 | <bt:String id="ME4W.GetStarted.Title" DefaultValue="Markdown Writer for Word" /> 83 | </bt:ShortStrings> 84 | <bt:LongStrings> 85 | <bt:String id="ME4W.TaskpaneButton.Tooltip" DefaultValue="Click to launch Markdown Writer" /> 86 | <bt:String id="ME4W.GetStarted.Description" DefaultValue="Create and edit markdown files and push them to GitHub." /> 87 | </bt:LongStrings> 88 | </Resources> 89 | </VersionOverrides> 90 | </OfficeApp> -------------------------------------------------------------------------------- /MarkdownEditorForWordAddin/MarkdownEditorForWordAddin/SharePointProjectItem.spdata: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <ProjectItem Type="Microsoft.VisualStudio.SharePoint.OfficeApp" DefaultFile="MarkdownEditorForWordAddinManifest.xml" SupportedTrustLevels="All" SupportedDeploymentScopes="AppPackage" xmlns="http://schemas.microsoft.com/VisualStudio/2010/SharePointTools/SharePointProjectItemModel"> 3 | <Files> 4 | <ProjectItemFile Source="MarkdownEditorForWordAddinManifest.xml" Type="AppPackage" /> 5 | </Files> 6 | <ExtensionData> 7 | <ExtensionDataItem Key="OfficeAppManifestRelativePath" Value="MarkdownEditorForWordAddin\MarkdownEditorForWordAddinManifest.xml" /> 8 | </ExtensionData> 9 | </ProjectItem> -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [ARCHIVED] Word-Add-in-Markdown-Editor 2 | 3 | **Note:** This repo is archived and no longer actively maintained. Security vulnerabilities may exist in the project, or its dependencies. If you plan to reuse or run any code from this repo, be sure to perform appropriate security checks on the code or dependencies first. Do not use this project as the starting point of a production Office Add-in. Always start your production code by using the Office/SharePoint development workload in Visual Studio, or the [Yeoman generator for Office Add-ins](https://github.com/OfficeDev/generator-office), and follow security best practices as you develop the add-in. 4 | Learn how you can write in Word and convert the document to markdown, save to Github and vice versa. 5 | 6 | ## Table of Contents 7 | * [Change History](#change-history) 8 | * [Prerequisites](#prerequisites) 9 | * [Configure the project](#configure-the-project) 10 | * [Run the project](#run-the-project) 11 | * [Questions and comments](#questions-and-comments) 12 | * [Additional resources](#additional-resources) 13 | 14 | ## Change History 15 | 16 | November 2016: 17 | * Initial sample version. 18 | 19 | 20 | ## Prerequisites 21 | 22 | * Word 2016 for Windows, build 16.0.6912.1000 or later. 23 | * [Node and npm](https://nodejs.org/en/) 24 | * [Git Bash](https://git-scm.com/downloads) - You should use a later build as earlier builds can show an error when generating the certificates. 25 | * GitHub account. 26 | 27 | ## GitHub account 28 | - You’d use your GitHub account to sign-in when prompted by the add-in. 29 | - Your email address should be made public in GitHub. To do this, browse to your profile page on GitHub and make the email address public. 30 | - Your organizational memberships should be made public in GitHub. If they are not public, those repos will not be listed in the add-in. 31 | - The repositories you work with should be initialized and should have at least the Master branch. It may or may not have any files. 32 | - You must grant the add-in access to browse and edit your repos when presented with this information in the dialog during sign in. 33 | 34 | 35 | ## Configure the project 36 | 37 | Run the following commands from your Bash shell for this project: 38 | 39 | 1. Clone this repo to your local machine. 40 | 2. Navigate to **Word-Addin-Ang2-Word-to-MD/WordToGitHub** folder. Run```npm install``` command to install all of the dependencies in package.json. 41 | 3. Navigate to **Word-Addin-Ang2-Word-to-MD/WordToGitHub/certificates** folder. Run ```bash gen_cert.bat``` command to create the certificates needed to run this sample. 42 | * Then in the repo on your local machine, double-click ca.crt, and select **Install Certificate**. 43 | * Select **Local Machine** and select **Next** to continue. 44 | * Select **Place all certificates in the following store** and then select **Browse**. 45 | * Select **Trusted Root Certification Authorities** and then select **OK**. 46 | * Select **Next** and then **Finish** and now your certificate authority cert has been added to your certificate store. 47 | 4. Navigate to **Word-Addin-Ang2-Word-to-MD/WordToGitHub** folder. Run ```npm start``` command to start the node web server service. 48 | 49 | Now you need to let Microsoft Word know where to find the add-in. 50 | 51 | 1. Create a network share, or [share a folder to the network](https://technet.microsoft.com/en-us/library/cc770880.aspx) and place the [word-add-in-javascript-speckit-manifest.xml](word-add-in-javascript-speckit-manifest.xml) manifest file in it. 52 | 3. Launch Word and open a document. 53 | 4. Choose the **File** tab, and then choose **Options**. 54 | 5. Choose **Trust Center**, and then choose the **Trust Center Settings** button. 55 | 6. Choose **Trusted Add-ins Catalogs**. 56 | 7. In the **Catalog Url** field, enter the network path to the folder share that contains word-add-in-js-redact-manifest.xml, and then choose **Add Catalog**. 57 | 8. Select the **Show in Menu** check box, and then choose **OK**. 58 | 9. A message is displayed to inform you that your settings will be applied the next time you start Microsoft Office. Close and restart Word. 59 | 60 | ## Run the project 61 | 62 | 1. Open a Word document. 63 | 2. On the **Insert** tab in Word 2016, choose **My Add-ins**. 64 | 3. Select the **SHARED FOLDER** tab. 65 | 4. Choose **Word Redact add-in**, and then select **OK**. 66 | 5. If add-in commands are supported by your version of Word, the UI will inform you that the add-in was loaded. 67 | 68 | ### Launch the task pane 69 | 70 | On the Ribbon: 71 | * Select **Home** tab and choose **Edit markdown** to launch the task pane. 72 | 73 | > Note: The add-in will load in a task pane if add-in commands are not supported by your version of Word. 74 | 75 | ## Questions and comments 76 | 77 | We'd love to get your feedback about this sample. You can send your feedback to us in the *Issues* section of this repository. 78 | 79 | Questions about Microsoft Office 365 development in general should be posted to [Stack Overflow](http://stackoverflow.com/questions/tagged/office-js+API). Make sure that your questions are tagged with [office-js] and [API]. 80 | 81 | ## Additional resources 82 | 83 | * [Office add-in documentation](https://msdn.microsoft.com/en-us/library/office/jj220060.aspx) 84 | * [Office Dev Center](http://dev.office.com/) 85 | * [Office 365 APIs starter projects and code samples](http://msdn.microsoft.com/en-us/office/office365/howto/starter-projects-and-code-samples) 86 | 87 | ## Copyright 88 | Copyright (c) 2016 Microsoft Corporation. All rights reserved. 89 | 90 | 91 | 92 | 93 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 94 | -------------------------------------------------------------------------------- /Word-Add-in-Markdown-Editor.yml: -------------------------------------------------------------------------------- 1 | ### YamlMime:Sample 2 | sample: 3 | - name: 'Word add-in: Markdown editor' 4 | path: '' 5 | description: Learn how you can write in Word and convert the document to markdown, save to Github and vice versa. 6 | readme: '' 7 | generateZip: FALSE 8 | isLive: TRUE 9 | technologies: 10 | - Office Add-ins 11 | azureDeploy: '' 12 | author: umasubra 13 | platforms: [] 14 | languages: 15 | - JavaScript 16 | extensions: 17 | products: 18 | - Word 19 | scenarios: [] 20 | --------------------------------------------------------------------------------